const lottie = /* @__PURE__ */ (() => {
  if (
    typeof navigator === 'undefined' || navigator.product === 'ReactNative' ||
    typeof document === 'undefined' || typeof document.getElementsByTagName !== 'function' || typeof document.createElement !== 'function' ||
    typeof CanvasRenderingContext2D === 'undefined'
  )
    return {}

  const svgNS = 'http://www.w3.org/2000/svg'

  let locationHref = ''
  let _useWebWorker = false

  const initialDefaultFrame = -999999

  const setWebWorker = (flag) => {
    _useWebWorker = !!flag
  }
  const getWebWorker = () => _useWebWorker

  const setLocationHref = (value) => {
    locationHref = value
  }
  const getLocationHref = () => locationHref

  function createTag(type) {
    // return {appendChild:function(){},setAttribute:function(){},style:{}}
    return document.createElement(type)
  }

  function extendPrototype(sources, destination) {
    var i
    var len = sources.length
    var sourcePrototype
    for (i = 0; i < len; i += 1) {
      sourcePrototype = sources[i].prototype
      for (var attr in sourcePrototype) {
        if (Object.prototype.hasOwnProperty.call(sourcePrototype, attr))
          destination.prototype[attr] = sourcePrototype[attr]
      }
    }
  }

  function getDescriptor(object, prop) {
    return Object.getOwnPropertyDescriptor(object, prop)
  }

  function createProxyFunction(prototype) {
    function ProxyFunction() {}
    ProxyFunction.prototype = prototype
    return ProxyFunction
  }

  // import Howl from '../../3rd_party/howler';

  const audioControllerFactory = (function () {
    function AudioController(audioFactory) {
      this.audios = []
      this.audioFactory = audioFactory
      this._volume = 1
      this._isMuted = false
    }

    AudioController.prototype = {
      addAudio: function (audio) {
        this.audios.push(audio)
      },
      pause: function () {
        var i
        var len = this.audios.length
        for (i = 0; i < len; i += 1) {
          this.audios[i].pause()
        }
      },
      resume: function () {
        var i
        var len = this.audios.length
        for (i = 0; i < len; i += 1) {
          this.audios[i].resume()
        }
      },
      setRate: function (rateValue) {
        var i
        var len = this.audios.length
        for (i = 0; i < len; i += 1) {
          this.audios[i].setRate(rateValue)
        }
      },
      createAudio: function (assetPath) {
        if (this.audioFactory) {
          return this.audioFactory(assetPath)
        }
        if (window.Howl) {
          return new window.Howl({
            src: [assetPath],
          })
        }
        return {
          isPlaying: false,
          play: function () {
            this.isPlaying = true
          },
          seek: function () {
            this.isPlaying = false
          },
          playing: function () {},
          rate: function () {},
          setVolume: function () {},
        }
      },
      setAudioFactory: function (audioFactory) {
        this.audioFactory = audioFactory
      },
      setVolume: function (value) {
        this._volume = value
        this._updateVolume()
      },
      mute: function () {
        this._isMuted = true
        this._updateVolume()
      },
      unmute: function () {
        this._isMuted = false
        this._updateVolume()
      },
      getVolume: function () {
        return this._volume
      },
      _updateVolume: function () {
        var i
        var len = this.audios.length
        for (i = 0; i < len; i += 1) {
          this.audios[i].volume(this._volume * (this._isMuted ? 0 : 1))
        }
      },
    }

    return function () {
      return new AudioController()
    }
  })()

  const createTypedArray = (function () {
    function createRegularArray(type, len) {
      var i = 0
      var arr = []
      var value
      switch (type) {
        case 'int16':
        case 'uint8c':
          value = 1
          break
        default:
          value = 1.1
          break
      }
      for (i = 0; i < len; i += 1) {
        arr.push(value)
      }
      return arr
    }
    function createTypedArrayFactory(type, len) {
      if (type === 'float32') {
        return new Float32Array(len)
      }
      if (type === 'int16') {
        return new Int16Array(len)
      }
      if (type === 'uint8c') {
        return new Uint8ClampedArray(len)
      }
      return createRegularArray(type, len)
    }
    if (typeof Uint8ClampedArray === 'function' && typeof Float32Array === 'function') {
      return createTypedArrayFactory
    }
    return createRegularArray
  })()

  function createSizedArray(len) {
    return Array.apply(null, { length: len })
  }

  let subframeEnabled = true
  let expressionsPlugin = null
  let idPrefix$1 = ''
  const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
  let _shouldRoundValues = false
  const bmPow = Math.pow
  const bmSqrt = Math.sqrt
  const bmFloor = Math.floor
  const bmMax = Math.max
  const bmMin = Math.min

  const BMMath = {}
  ;(function () {
    var propertyNames = [
      'abs',
      'acos',
      'acosh',
      'asin',
      'asinh',
      'atan',
      'atanh',
      'atan2',
      'ceil',
      'cbrt',
      'expm1',
      'clz32',
      'cos',
      'cosh',
      'exp',
      'floor',
      'fround',
      'hypot',
      'imul',
      'log',
      'log1p',
      'log2',
      'log10',
      'max',
      'min',
      'pow',
      'random',
      'round',
      'sign',
      'sin',
      'sinh',
      'sqrt',
      'tan',
      'tanh',
      'trunc',
      'E',
      'LN10',
      'LN2',
      'LOG10E',
      'LOG2E',
      'PI',
      'SQRT1_2',
      'SQRT2',
    ]
    var i
    var len = propertyNames.length
    for (i = 0; i < len; i += 1) {
      BMMath[propertyNames[i]] = Math[propertyNames[i]]
    }
  })()

  function ProjectInterface$1() {
    return {}
  }
  BMMath.random = Math.random
  BMMath.abs = function (val) {
    var tOfVal = typeof val
    if (tOfVal === 'object' && val.length) {
      var absArr = createSizedArray(val.length)
      var i
      var len = val.length
      for (i = 0; i < len; i += 1) {
        absArr[i] = Math.abs(val[i])
      }
      return absArr
    }
    return Math.abs(val)
  }
  let defaultCurveSegments = 150
  const degToRads = Math.PI / 180
  const roundCorner = 0.5519

  function roundValues(flag) {
    _shouldRoundValues = !!flag
  }

  function bmRnd(value) {
    if (_shouldRoundValues) {
      return Math.round(value)
    }
    return value
  }

  function styleDiv(element) {
    element.style.position = 'absolute'
    element.style.top = 0
    element.style.left = 0
    element.style.display = 'block'
    element.style.transformOrigin = '0 0'
    element.style.webkitTransformOrigin = '0 0'
    element.style.backfaceVisibility = 'visible'
    element.style.webkitBackfaceVisibility = 'visible'
    element.style.transformStyle = 'preserve-3d'
    element.style.webkitTransformStyle = 'preserve-3d'
    element.style.mozTransformStyle = 'preserve-3d'
  }

  function BMEnterFrameEvent(type, currentTime, totalTime, frameMultiplier) {
    this.type = type
    this.currentTime = currentTime
    this.totalTime = totalTime
    this.direction = frameMultiplier < 0 ? -1 : 1
  }

  function BMCompleteEvent(type, frameMultiplier) {
    this.type = type
    this.direction = frameMultiplier < 0 ? -1 : 1
  }

  function BMCompleteLoopEvent(type, totalLoops, currentLoop, frameMultiplier) {
    this.type = type
    this.currentLoop = currentLoop
    this.totalLoops = totalLoops
    this.direction = frameMultiplier < 0 ? -1 : 1
  }

  function BMSegmentStartEvent(type, firstFrame, totalFrames) {
    this.type = type
    this.firstFrame = firstFrame
    this.totalFrames = totalFrames
  }

  function BMDestroyEvent(type, target) {
    this.type = type
    this.target = target
  }

  function BMRenderFrameErrorEvent(nativeError, currentTime) {
    this.type = 'renderFrameError'
    this.nativeError = nativeError
    this.currentTime = currentTime
  }

  function BMConfigErrorEvent(nativeError) {
    this.type = 'configError'
    this.nativeError = nativeError
  }

  function BMAnimationConfigErrorEvent(type, nativeError) {
    this.type = type
    this.nativeError = nativeError
  }

  const createElementID = (function () {
    var _count = 0
    return function createID() {
      _count += 1
      return idPrefix$1 + '__lottie_element_' + _count
    }
  })()

  function HSVtoRGB(h, s, v) {
    var r
    var g
    var b
    var i
    var f
    var p
    var q
    var t
    i = Math.floor(h * 6)
    f = h * 6 - i
    p = v * (1 - s)
    q = v * (1 - f * s)
    t = v * (1 - (1 - f) * s)
    switch (i % 6) {
      case 0:
        r = v
        g = t
        b = p
        break
      case 1:
        r = q
        g = v
        b = p
        break
      case 2:
        r = p
        g = v
        b = t
        break
      case 3:
        r = p
        g = q
        b = v
        break
      case 4:
        r = t
        g = p
        b = v
        break
      case 5:
        r = v
        g = p
        b = q
        break
      default:
        break
    }
    return [r, g, b]
  }

  function RGBtoHSV(r, g, b) {
    var max = Math.max(r, g, b)
    var min = Math.min(r, g, b)
    var d = max - min
    var h
    var s = max === 0 ? 0 : d / max
    var v = max / 255

    switch (max) {
      case min:
        h = 0
        break
      case r:
        h = g - b + d * (g < b ? 6 : 0)
        h /= 6 * d
        break
      case g:
        h = b - r + d * 2
        h /= 6 * d
        break
      case b:
        h = r - g + d * 4
        h /= 6 * d
        break
      default:
        break
    }

    return [h, s, v]
  }

  function addSaturationToRGB(color, offset) {
    var hsv = RGBtoHSV(color[0] * 255, color[1] * 255, color[2] * 255)
    hsv[1] += offset
    if (hsv[1] > 1) {
      hsv[1] = 1
    } else if (hsv[1] <= 0) {
      hsv[1] = 0
    }
    return HSVtoRGB(hsv[0], hsv[1], hsv[2])
  }

  function addBrightnessToRGB(color, offset) {
    var hsv = RGBtoHSV(color[0] * 255, color[1] * 255, color[2] * 255)
    hsv[2] += offset
    if (hsv[2] > 1) {
      hsv[2] = 1
    } else if (hsv[2] < 0) {
      hsv[2] = 0
    }
    return HSVtoRGB(hsv[0], hsv[1], hsv[2])
  }

  function addHueToRGB(color, offset) {
    var hsv = RGBtoHSV(color[0] * 255, color[1] * 255, color[2] * 255)
    hsv[0] += offset / 360
    if (hsv[0] > 1) {
      hsv[0] -= 1
    } else if (hsv[0] < 0) {
      hsv[0] += 1
    }
    return HSVtoRGB(hsv[0], hsv[1], hsv[2])
  }

  const rgbToHex = (function () {
    var colorMap = []
    var i
    var hex
    for (i = 0; i < 256; i += 1) {
      hex = i.toString(16)
      colorMap[i] = hex.length === 1 ? '0' + hex : hex
    }

    return function (r, g, b) {
      if (r < 0) {
        r = 0
      }
      if (g < 0) {
        g = 0
      }
      if (b < 0) {
        b = 0
      }
      return '#' + colorMap[r] + colorMap[g] + colorMap[b]
    }
  })()

  const setSubframeEnabled = (flag) => {
    subframeEnabled = !!flag
  }
  const getSubframeEnabled = () => subframeEnabled
  const setExpressionsPlugin = (value) => {
    expressionsPlugin = value
  }
  const getExpressionsPlugin = () => expressionsPlugin
  const setDefaultCurveSegments = (value) => {
    defaultCurveSegments = value
  }
  const getDefaultCurveSegments = () => defaultCurveSegments
  const setIdPrefix = (value) => {
    idPrefix$1 = value
  }
  const getIdPrefix = () => idPrefix$1

  function createNS(type) {
    // return {appendChild:function(){},setAttribute:function(){},style:{}}
    return document.createElementNS(svgNS, type)
  }

  const dataManager = (function () {
    var _counterId = 1
    var processes = []
    var workerFn
    var workerInstance
    var workerProxy = {
      onmessage: function () {},
      postMessage: function (path) {
        workerFn({
          data: path,
        })
      },
    }
    var _workerSelf = {
      postMessage: function (data) {
        workerProxy.onmessage({
          data: data,
        })
      },
    }
    function createWorker(fn) {
      if (window.Worker && window.Blob && getWebWorker()) {
        var blob = new Blob(['var _workerSelf = self; self.onmessage = ', fn.toString()], { type: 'text/javascript' })
        // var blob = new Blob(['self.onmessage = ', fn.toString()], { type: 'text/javascript' });
        var url = URL.createObjectURL(blob)
        return new Worker(url)
      }
      workerFn = fn
      return workerProxy
    }

    function setupWorker() {
      if (!workerInstance) {
        workerInstance = createWorker(function workerStart(e) {
          function dataFunctionManager() {
            function completeLayers(layers, comps) {
              var layerData
              var i
              var len = layers.length
              var j
              var jLen
              var k
              var kLen
              for (i = 0; i < len; i += 1) {
                layerData = layers[i]
                if ('ks' in layerData && !layerData.completed) {
                  layerData.completed = true
                  if (layerData.tt) {
                    layers[i - 1].td = layerData.tt
                  }
                  if (layerData.hasMask) {
                    var maskProps = layerData.masksProperties
                    jLen = maskProps.length
                    for (j = 0; j < jLen; j += 1) {
                      if (maskProps[j].pt.k.i) {
                        convertPathsToAbsoluteValues(maskProps[j].pt.k)
                      } else {
                        kLen = maskProps[j].pt.k.length
                        for (k = 0; k < kLen; k += 1) {
                          if (maskProps[j].pt.k[k].s) {
                            convertPathsToAbsoluteValues(maskProps[j].pt.k[k].s[0])
                          }
                          if (maskProps[j].pt.k[k].e) {
                            convertPathsToAbsoluteValues(maskProps[j].pt.k[k].e[0])
                          }
                        }
                      }
                    }
                  }
                  if (layerData.ty === 0) {
                    layerData.layers = findCompLayers(layerData.refId, comps)
                    completeLayers(layerData.layers, comps)
                  } else if (layerData.ty === 4) {
                    completeShapes(layerData.shapes)
                  } else if (layerData.ty === 5) {
                    completeText(layerData)
                  }
                }
              }
            }

            function completeChars(chars, assets) {
              if (chars) {
                var i = 0
                var len = chars.length
                for (i = 0; i < len; i += 1) {
                  if (chars[i].t === 1) {
                    // var compData = findComp(chars[i].data.refId, assets);
                    chars[i].data.layers = findCompLayers(chars[i].data.refId, assets)
                    // chars[i].data.ip = 0;
                    // chars[i].data.op = 99999;
                    // chars[i].data.st = 0;
                    // chars[i].data.sr = 1;
                    // chars[i].w = compData.w;
                    // chars[i].data.ks = {
                    //   a: { k: [0, 0, 0], a: 0 },
                    //   p: { k: [0, -compData.h, 0], a: 0 },
                    //   r: { k: 0, a: 0 },
                    //   s: { k: [100, 100], a: 0 },
                    //   o: { k: 100, a: 0 },
                    // };
                    completeLayers(chars[i].data.layers, assets)
                  }
                }
              }
            }

            function findComp(id, comps) {
              var i = 0
              var len = comps.length
              while (i < len) {
                if (comps[i].id === id) {
                  return comps[i]
                }
                i += 1
              }
              return null
            }

            function findCompLayers(id, comps) {
              var comp = findComp(id, comps)
              if (comp) {
                if (!comp.layers.__used) {
                  comp.layers.__used = true
                  return comp.layers
                }
                return JSON.parse(JSON.stringify(comp.layers))
              }
              return null
            }

            function completeShapes(arr) {
              var i
              var len = arr.length
              var j
              var jLen
              for (i = len - 1; i >= 0; i -= 1) {
                if (arr[i].ty === 'sh') {
                  if (arr[i].ks.k.i) {
                    convertPathsToAbsoluteValues(arr[i].ks.k)
                  } else {
                    jLen = arr[i].ks.k.length
                    for (j = 0; j < jLen; j += 1) {
                      if (arr[i].ks.k[j].s) {
                        convertPathsToAbsoluteValues(arr[i].ks.k[j].s[0])
                      }
                      if (arr[i].ks.k[j].e) {
                        convertPathsToAbsoluteValues(arr[i].ks.k[j].e[0])
                      }
                    }
                  }
                } else if (arr[i].ty === 'gr') {
                  completeShapes(arr[i].it)
                }
              }
            }

            function convertPathsToAbsoluteValues(path) {
              var i
              var len = path.i.length
              for (i = 0; i < len; i += 1) {
                path.i[i][0] += path.v[i][0]
                path.i[i][1] += path.v[i][1]
                path.o[i][0] += path.v[i][0]
                path.o[i][1] += path.v[i][1]
              }
            }

            function checkVersion(minimum, animVersionString) {
              var animVersion = animVersionString ? animVersionString.split('.') : [100, 100, 100]
              if (minimum[0] > animVersion[0]) {
                return true
              }
              if (animVersion[0] > minimum[0]) {
                return false
              }
              if (minimum[1] > animVersion[1]) {
                return true
              }
              if (animVersion[1] > minimum[1]) {
                return false
              }
              if (minimum[2] > animVersion[2]) {
                return true
              }
              if (animVersion[2] > minimum[2]) {
                return false
              }
              return null
            }

            var checkText = (function () {
              var minimumVersion = [4, 4, 14]

              function updateTextLayer(textLayer) {
                var documentData = textLayer.t.d
                textLayer.t.d = {
                  k: [
                    {
                      s: documentData,
                      t: 0,
                    },
                  ],
                }
              }

              function iterateLayers(layers) {
                var i
                var len = layers.length
                for (i = 0; i < len; i += 1) {
                  if (layers[i].ty === 5) {
                    updateTextLayer(layers[i])
                  }
                }
              }

              return function (animationData) {
                if (checkVersion(minimumVersion, animationData.v)) {
                  iterateLayers(animationData.layers)
                  if (animationData.assets) {
                    var i
                    var len = animationData.assets.length
                    for (i = 0; i < len; i += 1) {
                      if (animationData.assets[i].layers) {
                        iterateLayers(animationData.assets[i].layers)
                      }
                    }
                  }
                }
              }
            })()

            var checkChars = (function () {
              var minimumVersion = [4, 7, 99]
              return function (animationData) {
                if (animationData.chars && !checkVersion(minimumVersion, animationData.v)) {
                  var i
                  var len = animationData.chars.length
                  for (i = 0; i < len; i += 1) {
                    var charData = animationData.chars[i]
                    if (charData.data && charData.data.shapes) {
                      completeShapes(charData.data.shapes)
                      charData.data.ip = 0
                      charData.data.op = 99999
                      charData.data.st = 0
                      charData.data.sr = 1
                      charData.data.ks = {
                        p: { k: [0, 0], a: 0 },
                        s: { k: [100, 100], a: 0 },
                        a: { k: [0, 0], a: 0 },
                        r: { k: 0, a: 0 },
                        o: { k: 100, a: 0 },
                      }
                      if (!animationData.chars[i].t) {
                        charData.data.shapes.push({
                          ty: 'no',
                        })
                        charData.data.shapes[0].it.push({
                          p: { k: [0, 0], a: 0 },
                          s: { k: [100, 100], a: 0 },
                          a: { k: [0, 0], a: 0 },
                          r: { k: 0, a: 0 },
                          o: { k: 100, a: 0 },
                          sk: { k: 0, a: 0 },
                          sa: { k: 0, a: 0 },
                          ty: 'tr',
                        })
                      }
                    }
                  }
                }
              }
            })()

            var checkPathProperties = (function () {
              var minimumVersion = [5, 7, 15]

              function updateTextLayer(textLayer) {
                var pathData = textLayer.t.p
                if (typeof pathData.a === 'number') {
                  pathData.a = {
                    a: 0,
                    k: pathData.a,
                  }
                }
                if (typeof pathData.p === 'number') {
                  pathData.p = {
                    a: 0,
                    k: pathData.p,
                  }
                }
                if (typeof pathData.r === 'number') {
                  pathData.r = {
                    a: 0,
                    k: pathData.r,
                  }
                }
              }

              function iterateLayers(layers) {
                var i
                var len = layers.length
                for (i = 0; i < len; i += 1) {
                  if (layers[i].ty === 5) {
                    updateTextLayer(layers[i])
                  }
                }
              }

              return function (animationData) {
                if (checkVersion(minimumVersion, animationData.v)) {
                  iterateLayers(animationData.layers)
                  if (animationData.assets) {
                    var i
                    var len = animationData.assets.length
                    for (i = 0; i < len; i += 1) {
                      if (animationData.assets[i].layers) {
                        iterateLayers(animationData.assets[i].layers)
                      }
                    }
                  }
                }
              }
            })()

            var checkColors = (function () {
              var minimumVersion = [4, 1, 9]

              function iterateShapes(shapes) {
                var i
                var len = shapes.length
                var j
                var jLen
                for (i = 0; i < len; i += 1) {
                  if (shapes[i].ty === 'gr') {
                    iterateShapes(shapes[i].it)
                  } else if (shapes[i].ty === 'fl' || shapes[i].ty === 'st') {
                    if (shapes[i].c.k && shapes[i].c.k[0].i) {
                      jLen = shapes[i].c.k.length
                      for (j = 0; j < jLen; j += 1) {
                        if (shapes[i].c.k[j].s) {
                          shapes[i].c.k[j].s[0] /= 255
                          shapes[i].c.k[j].s[1] /= 255
                          shapes[i].c.k[j].s[2] /= 255
                          shapes[i].c.k[j].s[3] /= 255
                        }
                        if (shapes[i].c.k[j].e) {
                          shapes[i].c.k[j].e[0] /= 255
                          shapes[i].c.k[j].e[1] /= 255
                          shapes[i].c.k[j].e[2] /= 255
                          shapes[i].c.k[j].e[3] /= 255
                        }
                      }
                    } else {
                      shapes[i].c.k[0] /= 255
                      shapes[i].c.k[1] /= 255
                      shapes[i].c.k[2] /= 255
                      shapes[i].c.k[3] /= 255
                    }
                  }
                }
              }

              function iterateLayers(layers) {
                var i
                var len = layers.length
                for (i = 0; i < len; i += 1) {
                  if (layers[i].ty === 4) {
                    iterateShapes(layers[i].shapes)
                  }
                }
              }

              return function (animationData) {
                if (checkVersion(minimumVersion, animationData.v)) {
                  iterateLayers(animationData.layers)
                  if (animationData.assets) {
                    var i
                    var len = animationData.assets.length
                    for (i = 0; i < len; i += 1) {
                      if (animationData.assets[i].layers) {
                        iterateLayers(animationData.assets[i].layers)
                      }
                    }
                  }
                }
              }
            })()

            var checkShapes = (function () {
              var minimumVersion = [4, 4, 18]

              function completeClosingShapes(arr) {
                var i
                var len = arr.length
                var j
                var jLen
                for (i = len - 1; i >= 0; i -= 1) {
                  if (arr[i].ty === 'sh') {
                    if (arr[i].ks.k.i) {
                      arr[i].ks.k.c = arr[i].closed
                    } else {
                      jLen = arr[i].ks.k.length
                      for (j = 0; j < jLen; j += 1) {
                        if (arr[i].ks.k[j].s) {
                          arr[i].ks.k[j].s[0].c = arr[i].closed
                        }
                        if (arr[i].ks.k[j].e) {
                          arr[i].ks.k[j].e[0].c = arr[i].closed
                        }
                      }
                    }
                  } else if (arr[i].ty === 'gr') {
                    completeClosingShapes(arr[i].it)
                  }
                }
              }

              function iterateLayers(layers) {
                var layerData
                var i
                var len = layers.length
                var j
                var jLen
                var k
                var kLen
                for (i = 0; i < len; i += 1) {
                  layerData = layers[i]
                  if (layerData.hasMask) {
                    var maskProps = layerData.masksProperties
                    jLen = maskProps.length
                    for (j = 0; j < jLen; j += 1) {
                      if (maskProps[j].pt.k.i) {
                        maskProps[j].pt.k.c = maskProps[j].cl
                      } else {
                        kLen = maskProps[j].pt.k.length
                        for (k = 0; k < kLen; k += 1) {
                          if (maskProps[j].pt.k[k].s) {
                            maskProps[j].pt.k[k].s[0].c = maskProps[j].cl
                          }
                          if (maskProps[j].pt.k[k].e) {
                            maskProps[j].pt.k[k].e[0].c = maskProps[j].cl
                          }
                        }
                      }
                    }
                  }
                  if (layerData.ty === 4) {
                    completeClosingShapes(layerData.shapes)
                  }
                }
              }

              return function (animationData) {
                if (checkVersion(minimumVersion, animationData.v)) {
                  iterateLayers(animationData.layers)
                  if (animationData.assets) {
                    var i
                    var len = animationData.assets.length
                    for (i = 0; i < len; i += 1) {
                      if (animationData.assets[i].layers) {
                        iterateLayers(animationData.assets[i].layers)
                      }
                    }
                  }
                }
              }
            })()

            function completeData(animationData) {
              if (animationData.__complete) {
                return
              }
              checkColors(animationData)
              checkText(animationData)
              checkChars(animationData)
              checkPathProperties(animationData)
              checkShapes(animationData)
              completeLayers(animationData.layers, animationData.assets)
              completeChars(animationData.chars, animationData.assets)
              animationData.__complete = true
            }

            function completeText(data) {
              if (data.t.a.length === 0 && !('m' in data.t.p)) {
                // data.singleShape = true;
              }
            }

            var moduleOb = {}
            moduleOb.completeData = completeData
            moduleOb.checkColors = checkColors
            moduleOb.checkChars = checkChars
            moduleOb.checkPathProperties = checkPathProperties
            moduleOb.checkShapes = checkShapes
            moduleOb.completeLayers = completeLayers

            return moduleOb
          }
          if (!_workerSelf.dataManager) {
            _workerSelf.dataManager = dataFunctionManager()
          }

          if (!_workerSelf.assetLoader) {
            _workerSelf.assetLoader = (function () {
              function formatResponse(xhr) {
                // using typeof doubles the time of execution of this method,
                // so if available, it's better to use the header to validate the type
                var contentTypeHeader = xhr.getResponseHeader('content-type')
                if (contentTypeHeader && xhr.responseType === 'json' && contentTypeHeader.indexOf('json') !== -1) {
                  return xhr.response
                }
                if (xhr.response && typeof xhr.response === 'object') {
                  return xhr.response
                }
                if (xhr.response && typeof xhr.response === 'string') {
                  return JSON.parse(xhr.response)
                }
                if (xhr.responseText) {
                  return JSON.parse(xhr.responseText)
                }
                return null
              }

              function loadAsset(path, fullPath, callback, errorCallback) {
                var response
                var xhr = new XMLHttpRequest()
                // set responseType after calling open or IE will break.
                try {
                  // This crashes on Android WebView prior to KitKat
                  xhr.responseType = 'json'
                } catch (err) {} // eslint-disable-line no-empty
                xhr.onreadystatechange = function () {
                  if (xhr.readyState === 4) {
                    if (xhr.status === 200) {
                      response = formatResponse(xhr)
                      callback(response)
                    } else {
                      try {
                        response = formatResponse(xhr)
                        callback(response)
                      } catch (err) {
                        if (errorCallback) {
                          errorCallback(err)
                        }
                      }
                    }
                  }
                }
                try {
                  xhr.open('GET', path, true)
                } catch (error) {
                  xhr.open('GET', fullPath + '/' + path, true)
                }
                xhr.send()
              }
              return {
                load: loadAsset,
              }
            })()
          }

          if (e.data.type === 'loadAnimation') {
            _workerSelf.assetLoader.load(
              e.data.path,
              e.data.fullPath,
              function (data) {
                _workerSelf.dataManager.completeData(data)
                _workerSelf.postMessage({
                  id: e.data.id,
                  payload: data,
                  status: 'success',
                })
              },
              function () {
                _workerSelf.postMessage({
                  id: e.data.id,
                  status: 'error',
                })
              },
            )
          } else if (e.data.type === 'complete') {
            var animation = e.data.animation
            _workerSelf.dataManager.completeData(animation)
            _workerSelf.postMessage({
              id: e.data.id,
              payload: animation,
              status: 'success',
            })
          } else if (e.data.type === 'loadData') {
            _workerSelf.assetLoader.load(
              e.data.path,
              e.data.fullPath,
              function (data) {
                _workerSelf.postMessage({
                  id: e.data.id,
                  payload: data,
                  status: 'success',
                })
              },
              function () {
                _workerSelf.postMessage({
                  id: e.data.id,
                  status: 'error',
                })
              },
            )
          }
        })

        workerInstance.onmessage = function (event) {
          var data = event.data
          var id = data.id
          var process = processes[id]
          processes[id] = null
          if (data.status === 'success') {
            process.onComplete(data.payload)
          } else if (process.onError) {
            process.onError()
          }
        }
      }
    }

    function createProcess(onComplete, onError) {
      _counterId += 1
      var id = 'processId_' + _counterId
      processes[id] = {
        onComplete: onComplete,
        onError: onError,
      }
      return id
    }

    function loadAnimation(path, onComplete, onError) {
      setupWorker()
      var processId = createProcess(onComplete, onError)
      workerInstance.postMessage({
        type: 'loadAnimation',
        path: path,
        fullPath: window.location.origin + window.location.pathname,
        id: processId,
      })
    }

    function loadData(path, onComplete, onError) {
      setupWorker()
      var processId = createProcess(onComplete, onError)
      workerInstance.postMessage({
        type: 'loadData',
        path: path,
        fullPath: window.location.origin + window.location.pathname,
        id: processId,
      })
    }

    function completeAnimation(anim, onComplete, onError) {
      setupWorker()
      var processId = createProcess(onComplete, onError)
      workerInstance.postMessage({
        type: 'complete',
        animation: anim,
        id: processId,
      })
    }

    return {
      loadAnimation: loadAnimation,
      loadData: loadData,
      completeAnimation: completeAnimation,
    }
  })()

  const ImagePreloader = (function () {
    var proxyImage = (function () {
      var canvas = createTag('canvas')
      canvas.width = 1
      canvas.height = 1
      var ctx = canvas.getContext('2d')
      ctx.fillStyle = 'rgba(0,0,0,0)'
      ctx.fillRect(0, 0, 1, 1)
      return canvas
    })()

    function imageLoaded() {
      this.loadedAssets += 1
      if (this.loadedAssets === this.totalImages && this.loadedFootagesCount === this.totalFootages) {
        if (this.imagesLoadedCb) {
          this.imagesLoadedCb(null)
        }
      }
    }
    function footageLoaded() {
      this.loadedFootagesCount += 1
      if (this.loadedAssets === this.totalImages && this.loadedFootagesCount === this.totalFootages) {
        if (this.imagesLoadedCb) {
          this.imagesLoadedCb(null)
        }
      }
    }

    function getAssetsPath(assetData, assetsPath, originalPath) {
      var path = ''
      if (assetData.e) {
        path = assetData.p
      } else if (assetsPath) {
        var imagePath = assetData.p
        if (imagePath.indexOf('images/') !== -1) {
          imagePath = imagePath.split('/')[1]
        }
        path = assetsPath + imagePath
      } else {
        path = originalPath
        path += assetData.u ? assetData.u : ''
        path += assetData.p
      }
      return path
    }

    function testImageLoaded(img) {
      var _count = 0
      var intervalId = setInterval(
        function () {
          var box = img.getBBox()
          if (box.width || _count > 500) {
            this._imageLoaded()
            clearInterval(intervalId)
          }
          _count += 1
        }.bind(this),
        50,
      )
    }

    function createImageData(assetData) {
      var path = getAssetsPath(assetData, this.assetsPath, this.path)
      var img = createNS('image')
      if (isSafari) {
        this.testImageLoaded(img)
      } else {
        img.addEventListener('load', this._imageLoaded, false)
      }
      img.addEventListener(
        'error',
        function () {
          ob.img = proxyImage
          this._imageLoaded()
        }.bind(this),
        false,
      )
      img.setAttributeNS('http://www.w3.org/1999/xlink', 'href', path)
      if (this._elementHelper.append) {
        this._elementHelper.append(img)
      } else {
        this._elementHelper.appendChild(img)
      }
      var ob = {
        img: img,
        assetData: assetData,
      }
      return ob
    }

    function createImgData(assetData) {
      var path = getAssetsPath(assetData, this.assetsPath, this.path)
      var img = createTag('img')
      img.crossOrigin = 'anonymous'
      img.addEventListener('load', this._imageLoaded, false)
      img.addEventListener(
        'error',
        function () {
          ob.img = proxyImage
          this._imageLoaded()
        }.bind(this),
        false,
      )
      img.src = path
      var ob = {
        img: img,
        assetData: assetData,
      }
      return ob
    }

    function createFootageData(data) {
      var ob = {
        assetData: data,
      }
      var path = getAssetsPath(data, this.assetsPath, this.path)
      dataManager.loadData(
        path,
        function (footageData) {
          ob.img = footageData
          this._footageLoaded()
        }.bind(this),
        function () {
          ob.img = {}
          this._footageLoaded()
        }.bind(this),
      )
      return ob
    }

    function loadAssets(assets, cb) {
      this.imagesLoadedCb = cb
      var i
      var len = assets.length
      for (i = 0; i < len; i += 1) {
        if (!assets[i].layers) {
          if (!assets[i].t || assets[i].t === 'seq') {
            this.totalImages += 1
            this.images.push(this._createImageData(assets[i]))
          } else if (assets[i].t === 3) {
            this.totalFootages += 1
            this.images.push(this.createFootageData(assets[i]))
          }
        }
      }
    }

    function setPath(path) {
      this.path = path || ''
    }

    function setAssetsPath(path) {
      this.assetsPath = path || ''
    }

    function getAsset(assetData) {
      var i = 0
      var len = this.images.length
      while (i < len) {
        if (this.images[i].assetData === assetData) {
          return this.images[i].img
        }
        i += 1
      }
      return null
    }

    function destroy() {
      this.imagesLoadedCb = null
      this.images.length = 0
    }

    function loadedImages() {
      return this.totalImages === this.loadedAssets
    }

    function loadedFootages() {
      return this.totalFootages === this.loadedFootagesCount
    }

    function setCacheType(type, elementHelper) {
      if (type === 'svg') {
        this._elementHelper = elementHelper
        this._createImageData = this.createImageData.bind(this)
      } else {
        this._createImageData = this.createImgData.bind(this)
      }
    }

    function ImagePreloaderFactory() {
      this._imageLoaded = imageLoaded.bind(this)
      this._footageLoaded = footageLoaded.bind(this)
      this.testImageLoaded = testImageLoaded.bind(this)
      this.createFootageData = createFootageData.bind(this)
      this.assetsPath = ''
      this.path = ''
      this.totalImages = 0
      this.totalFootages = 0
      this.loadedAssets = 0
      this.loadedFootagesCount = 0
      this.imagesLoadedCb = null
      this.images = []
    }

    ImagePreloaderFactory.prototype = {
      loadAssets: loadAssets,
      setAssetsPath: setAssetsPath,
      setPath: setPath,
      loadedImages: loadedImages,
      loadedFootages: loadedFootages,
      destroy: destroy,
      getAsset: getAsset,
      createImgData: createImgData,
      createImageData: createImageData,
      imageLoaded: imageLoaded,
      footageLoaded: footageLoaded,
      setCacheType: setCacheType,
    }

    return ImagePreloaderFactory
  })()

  function BaseEvent() {}
  BaseEvent.prototype = {
    triggerEvent: function (eventName, args) {
      if (this._cbs[eventName]) {
        var callbacks = this._cbs[eventName]
        for (var i = 0; i < callbacks.length; i += 1) {
          callbacks[i](args)
        }
      }
    },
    addEventListener: function (eventName, callback) {
      if (!this._cbs[eventName]) {
        this._cbs[eventName] = []
      }
      this._cbs[eventName].push(callback)

      return function () {
        this.removeEventListener(eventName, callback)
      }.bind(this)
    },
    removeEventListener: function (eventName, callback) {
      if (!callback) {
        this._cbs[eventName] = null
      } else if (this._cbs[eventName]) {
        var i = 0
        var len = this._cbs[eventName].length
        while (i < len) {
          if (this._cbs[eventName][i] === callback) {
            this._cbs[eventName].splice(i, 1)
            i -= 1
            len -= 1
          }
          i += 1
        }
        if (!this._cbs[eventName].length) {
          this._cbs[eventName] = null
        }
      }
    },
  }

  const markerParser = (function () {
    function parsePayloadLines(payload) {
      var lines = payload.split('\r\n')
      var keys = {}
      var line
      var keysCount = 0
      for (var i = 0; i < lines.length; i += 1) {
        line = lines[i].split(':')
        if (line.length === 2) {
          keys[line[0]] = line[1].trim()
          keysCount += 1
        }
      }
      if (keysCount === 0) {
        throw new Error()
      }
      return keys
    }

    return function (_markers) {
      var markers = []
      for (var i = 0; i < _markers.length; i += 1) {
        var _marker = _markers[i]
        var markerData = {
          time: _marker.tm,
          duration: _marker.dr,
        }
        try {
          markerData.payload = JSON.parse(_markers[i].cm)
        } catch (_) {
          try {
            markerData.payload = parsePayloadLines(_markers[i].cm)
          } catch (__) {
            markerData.payload = {
              name: _markers[i].cm,
            }
          }
        }
        markers.push(markerData)
      }
      return markers
    }
  })()

  const ProjectInterface = (function () {
    function registerComposition(comp) {
      this.compositions.push(comp)
    }

    return function () {
      function _thisProjectFunction(name) {
        var i = 0
        var len = this.compositions.length
        while (i < len) {
          if (this.compositions[i].data && this.compositions[i].data.nm === name) {
            if (this.compositions[i].prepareFrame && this.compositions[i].data.xt) {
              this.compositions[i].prepareFrame(this.currentFrame)
            }
            return this.compositions[i].compInterface
          }
          i += 1
        }
        return null
      }

      _thisProjectFunction.compositions = []
      _thisProjectFunction.currentFrame = 0

      _thisProjectFunction.registerComposition = registerComposition

      return _thisProjectFunction
    }
  })()

  const renderers = {}

  const registerRenderer = (key, value) => {
    renderers[key] = value
  }

  function getRenderer(key) {
    return renderers[key]
  }

  const AnimationItem = function () {
    this._cbs = []
    this.name = ''
    this.path = ''
    this.isLoaded = false
    this.currentFrame = 0
    this.currentRawFrame = 0
    this.firstFrame = 0
    this.totalFrames = 0
    this.frameRate = 0
    this.frameMult = 0
    this.playSpeed = 1
    this.playDirection = 1
    this.playCount = 0
    this.animationData = {}
    this.assets = []
    this.isPaused = true
    this.autoplay = false
    this.loop = true
    this.renderer = null
    this.animationID = createElementID()
    this.assetsPath = ''
    this.timeCompleted = 0
    this.segmentPos = 0
    this.isSubframeEnabled = getSubframeEnabled()
    this.segments = []
    this._idle = true
    this._completedLoop = false
    this.projectInterface = ProjectInterface()
    this.imagePreloader = new ImagePreloader()
    this.audioController = audioControllerFactory()
    this.markers = []
    this.configAnimation = this.configAnimation.bind(this)
    this.onSetupError = this.onSetupError.bind(this)
    this.onSegmentComplete = this.onSegmentComplete.bind(this)
    this.drawnFrameEvent = new BMEnterFrameEvent('drawnFrame', 0, 0, 0)
  }

  extendPrototype([BaseEvent], AnimationItem)

  AnimationItem.prototype.setParams = function (params) {
    if (params.wrapper || params.container) {
      this.wrapper = params.wrapper || params.container
    }
    var animType = 'svg'
    if (params.animType) {
      animType = params.animType
    } else if (params.renderer) {
      animType = params.renderer
    }
    const RendererClass = getRenderer(animType)
    this.renderer = new RendererClass(this, params.rendererSettings)
    this.imagePreloader.setCacheType(animType, this.renderer.globalData.defs)
    this.renderer.setProjectInterface(this.projectInterface)
    this.animType = animType
    if (params.loop === '' || params.loop === null || params.loop === undefined || params.loop === true) {
      this.loop = true
    } else if (params.loop === false) {
      this.loop = false
    } else {
      this.loop = parseInt(params.loop, 10)
    }
    this.autoplay = 'autoplay' in params ? params.autoplay : true
    this.name = params.name ? params.name : ''
    this.autoloadSegments = Object.prototype.hasOwnProperty.call(params, 'autoloadSegments')
      ? params.autoloadSegments
      : true
    this.assetsPath = params.assetsPath
    this.initialSegment = params.initialSegment
    if (params.audioFactory) {
      this.audioController.setAudioFactory(params.audioFactory)
    }
    if (params.animationData) {
      this.setupAnimation(params.animationData)
    } else if (params.path) {
      if (params.path.lastIndexOf('\\') !== -1) {
        this.path = params.path.substr(0, params.path.lastIndexOf('\\') + 1)
      } else {
        this.path = params.path.substr(0, params.path.lastIndexOf('/') + 1)
      }
      this.fileName = params.path.substr(params.path.lastIndexOf('/') + 1)
      this.fileName = this.fileName.substr(0, this.fileName.lastIndexOf('.json'))
      dataManager.loadAnimation(params.path, this.configAnimation, this.onSetupError)
    }
  }

  AnimationItem.prototype.onSetupError = function () {
    this.trigger('data_failed')
  }

  AnimationItem.prototype.setupAnimation = function (data) {
    dataManager.completeAnimation(data, this.configAnimation)
  }

  AnimationItem.prototype.setData = function (wrapper, animationData) {
    if (animationData) {
      if (typeof animationData !== 'object') {
        animationData = JSON.parse(animationData)
      }
    }
    var params = {
      wrapper: wrapper,
      animationData: animationData,
    }
    var wrapperAttributes = wrapper.attributes

    params.path = wrapperAttributes.getNamedItem('data-animation-path') // eslint-disable-line no-nested-ternary
      ? wrapperAttributes.getNamedItem('data-animation-path').value
      : wrapperAttributes.getNamedItem('data-bm-path') // eslint-disable-line no-nested-ternary
      ? wrapperAttributes.getNamedItem('data-bm-path').value
      : wrapperAttributes.getNamedItem('bm-path')
      ? wrapperAttributes.getNamedItem('bm-path').value
      : ''
    params.animType = wrapperAttributes.getNamedItem('data-anim-type') // eslint-disable-line no-nested-ternary
      ? wrapperAttributes.getNamedItem('data-anim-type').value
      : wrapperAttributes.getNamedItem('data-bm-type') // eslint-disable-line no-nested-ternary
      ? wrapperAttributes.getNamedItem('data-bm-type').value
      : wrapperAttributes.getNamedItem('bm-type') // eslint-disable-line no-nested-ternary
      ? wrapperAttributes.getNamedItem('bm-type').value
      : wrapperAttributes.getNamedItem('data-bm-renderer') // eslint-disable-line no-nested-ternary
      ? wrapperAttributes.getNamedItem('data-bm-renderer').value
      : wrapperAttributes.getNamedItem('bm-renderer')
      ? wrapperAttributes.getNamedItem('bm-renderer').value
      : 'canvas'

    var loop = wrapperAttributes.getNamedItem('data-anim-loop') // eslint-disable-line no-nested-ternary
      ? wrapperAttributes.getNamedItem('data-anim-loop').value
      : wrapperAttributes.getNamedItem('data-bm-loop') // eslint-disable-line no-nested-ternary
      ? wrapperAttributes.getNamedItem('data-bm-loop').value
      : wrapperAttributes.getNamedItem('bm-loop')
      ? wrapperAttributes.getNamedItem('bm-loop').value
      : ''
    if (loop === 'false') {
      params.loop = false
    } else if (loop === 'true') {
      params.loop = true
    } else if (loop !== '') {
      params.loop = parseInt(loop, 10)
    }
    var autoplay = wrapperAttributes.getNamedItem('data-anim-autoplay') // eslint-disable-line no-nested-ternary
      ? wrapperAttributes.getNamedItem('data-anim-autoplay').value
      : wrapperAttributes.getNamedItem('data-bm-autoplay') // eslint-disable-line no-nested-ternary
      ? wrapperAttributes.getNamedItem('data-bm-autoplay').value
      : wrapperAttributes.getNamedItem('bm-autoplay')
      ? wrapperAttributes.getNamedItem('bm-autoplay').value
      : true
    params.autoplay = autoplay !== 'false'

    params.name = wrapperAttributes.getNamedItem('data-name') // eslint-disable-line no-nested-ternary
      ? wrapperAttributes.getNamedItem('data-name').value
      : wrapperAttributes.getNamedItem('data-bm-name') // eslint-disable-line no-nested-ternary
      ? wrapperAttributes.getNamedItem('data-bm-name').value
      : wrapperAttributes.getNamedItem('bm-name')
      ? wrapperAttributes.getNamedItem('bm-name').value
      : ''
    var prerender = wrapperAttributes.getNamedItem('data-anim-prerender') // eslint-disable-line no-nested-ternary
      ? wrapperAttributes.getNamedItem('data-anim-prerender').value
      : wrapperAttributes.getNamedItem('data-bm-prerender') // eslint-disable-line no-nested-ternary
      ? wrapperAttributes.getNamedItem('data-bm-prerender').value
      : wrapperAttributes.getNamedItem('bm-prerender')
      ? wrapperAttributes.getNamedItem('bm-prerender').value
      : ''

    if (prerender === 'false') {
      params.prerender = false
    }
    this.setParams(params)
  }

  AnimationItem.prototype.includeLayers = function (data) {
    if (data.op > this.animationData.op) {
      this.animationData.op = data.op
      this.totalFrames = Math.floor(data.op - this.animationData.ip)
    }
    var layers = this.animationData.layers
    var i
    var len = layers.length
    var newLayers = data.layers
    var j
    var jLen = newLayers.length
    for (j = 0; j < jLen; j += 1) {
      i = 0
      while (i < len) {
        if (layers[i].id === newLayers[j].id) {
          layers[i] = newLayers[j]
          break
        }
        i += 1
      }
    }
    if (data.chars || data.fonts) {
      this.renderer.globalData.fontManager.addChars(data.chars)
      this.renderer.globalData.fontManager.addFonts(data.fonts, this.renderer.globalData.defs)
    }
    if (data.assets) {
      len = data.assets.length
      for (i = 0; i < len; i += 1) {
        this.animationData.assets.push(data.assets[i])
      }
    }
    this.animationData.__complete = false
    dataManager.completeAnimation(this.animationData, this.onSegmentComplete)
  }

  AnimationItem.prototype.onSegmentComplete = function (data) {
    this.animationData = data
    var expressionsPlugin = getExpressionsPlugin()
    if (expressionsPlugin) {
      expressionsPlugin.initExpressions(this)
    }
    this.loadNextSegment()
  }

  AnimationItem.prototype.loadNextSegment = function () {
    var segments = this.animationData.segments
    if (!segments || segments.length === 0 || !this.autoloadSegments) {
      this.trigger('data_ready')
      this.timeCompleted = this.totalFrames
      return
    }
    var segment = segments.shift()
    this.timeCompleted = segment.time * this.frameRate
    var segmentPath = this.path + this.fileName + '_' + this.segmentPos + '.json'
    this.segmentPos += 1
    dataManager.loadData(
      segmentPath,
      this.includeLayers.bind(this),
      function () {
        this.trigger('data_failed')
      }.bind(this),
    )
  }

  AnimationItem.prototype.loadSegments = function () {
    var segments = this.animationData.segments
    if (!segments) {
      this.timeCompleted = this.totalFrames
    }
    this.loadNextSegment()
  }

  AnimationItem.prototype.imagesLoaded = function () {
    this.trigger('loaded_images')
    this.checkLoaded()
  }

  AnimationItem.prototype.preloadImages = function () {
    this.imagePreloader.setAssetsPath(this.assetsPath)
    this.imagePreloader.setPath(this.path)
    this.imagePreloader.loadAssets(this.animationData.assets, this.imagesLoaded.bind(this))
  }

  AnimationItem.prototype.configAnimation = function (animData) {
    if (!this.renderer) {
      return
    }
    try {
      this.animationData = animData
      if (this.initialSegment) {
        this.totalFrames = Math.floor(this.initialSegment[1] - this.initialSegment[0])
        this.firstFrame = Math.round(this.initialSegment[0])
      } else {
        this.totalFrames = Math.floor(this.animationData.op - this.animationData.ip)
        this.firstFrame = Math.round(this.animationData.ip)
      }
      this.renderer.configAnimation(animData)
      if (!animData.assets) {
        animData.assets = []
      }

      this.assets = this.animationData.assets
      this.frameRate = this.animationData.fr
      this.frameMult = this.animationData.fr / 1000
      this.renderer.searchExtraCompositions(animData.assets)
      this.markers = markerParser(animData.markers || [])
      this.trigger('config_ready')
      this.preloadImages()
      this.loadSegments()
      this.updaFrameModifier()
      this.waitForFontsLoaded()
      if (this.isPaused) {
        this.audioController.pause()
      }
    } catch (error) {
      this.triggerConfigError(error)
    }
  }

  AnimationItem.prototype.waitForFontsLoaded = function () {
    if (!this.renderer) {
      return
    }
    if (this.renderer.globalData.fontManager.isLoaded) {
      this.checkLoaded()
    } else {
      setTimeout(this.waitForFontsLoaded.bind(this), 20)
    }
  }

  AnimationItem.prototype.checkLoaded = function () {
    if (
      !this.isLoaded &&
      this.renderer.globalData.fontManager.isLoaded &&
      (this.imagePreloader.loadedImages() || this.renderer.rendererType !== 'canvas') &&
      this.imagePreloader.loadedFootages()
    ) {
      this.isLoaded = true
      var expressionsPlugin = getExpressionsPlugin()
      if (expressionsPlugin) {
        expressionsPlugin.initExpressions(this)
      }
      this.renderer.initItems()
      setTimeout(
        function () {
          this.trigger('DOMLoaded')
        }.bind(this),
        0,
      )
      this.gotoFrame()
      if (this.autoplay) {
        this.play()
      }
    }
  }

  AnimationItem.prototype.resize = function () {
    this.renderer.updateContainerSize()
  }

  AnimationItem.prototype.setSubframe = function (flag) {
    this.isSubframeEnabled = !!flag
  }

  AnimationItem.prototype.gotoFrame = function () {
    this.currentFrame = this.isSubframeEnabled ? this.currentRawFrame : ~~this.currentRawFrame // eslint-disable-line no-bitwise

    if (this.timeCompleted !== this.totalFrames && this.currentFrame > this.timeCompleted) {
      this.currentFrame = this.timeCompleted
    }
    this.trigger('enterFrame')
    this.renderFrame()
    this.trigger('drawnFrame')
  }

  AnimationItem.prototype.renderFrame = function () {
    if (this.isLoaded === false || !this.renderer) {
      return
    }
    try {
      this.renderer.renderFrame(this.currentFrame + this.firstFrame)
    } catch (error) {
      this.triggerRenderFrameError(error)
    }
  }

  AnimationItem.prototype.play = function (name) {
    if (name && this.name !== name) {
      return
    }
    if (this.isPaused === true) {
      this.isPaused = false
      this.trigger('_pause')
      this.audioController.resume()
      if (this._idle) {
        this._idle = false
        this.trigger('_active')
      }
    }
  }

  AnimationItem.prototype.pause = function (name) {
    if (name && this.name !== name) {
      return
    }
    if (this.isPaused === false) {
      this.isPaused = true
      this.trigger('_play')
      this._idle = true
      this.trigger('_idle')
      this.audioController.pause()
    }
  }

  AnimationItem.prototype.togglePause = function (name) {
    if (name && this.name !== name) {
      return
    }
    if (this.isPaused === true) {
      this.play()
    } else {
      this.pause()
    }
  }

  AnimationItem.prototype.stop = function (name) {
    if (name && this.name !== name) {
      return
    }
    this.pause()
    this.playCount = 0
    this._completedLoop = false
    this.setCurrentRawFrameValue(0)
  }

  AnimationItem.prototype.getMarkerData = function (markerName) {
    var marker
    for (var i = 0; i < this.markers.length; i += 1) {
      marker = this.markers[i]
      if (marker.payload && marker.payload.name === markerName) {
        return marker
      }
    }
    return null
  }

  AnimationItem.prototype.goToAndStop = function (value, isFrame, name) {
    if (name && this.name !== name) {
      return
    }
    var numValue = Number(value)
    if (isNaN(numValue)) {
      var marker = this.getMarkerData(value)
      if (marker) {
        this.goToAndStop(marker.time, true)
      }
    } else if (isFrame) {
      this.setCurrentRawFrameValue(value)
    } else {
      this.setCurrentRawFrameValue(value * this.frameModifier)
    }
    this.pause()
  }

  AnimationItem.prototype.goToAndPlay = function (value, isFrame, name) {
    if (name && this.name !== name) {
      return
    }
    var numValue = Number(value)
    if (isNaN(numValue)) {
      var marker = this.getMarkerData(value)
      if (marker) {
        if (!marker.duration) {
          this.goToAndStop(marker.time, true)
        } else {
          this.playSegments([marker.time, marker.time + marker.duration], true)
        }
      }
    } else {
      this.goToAndStop(numValue, isFrame, name)
    }
    this.play()
  }

  AnimationItem.prototype.advanceTime = function (value) {
    if (this.isPaused === true || this.isLoaded === false) {
      return
    }
    var nextValue = this.currentRawFrame + value * this.frameModifier
    var _isComplete = false
    // Checking if nextValue > totalFrames - 1 for addressing non looping and looping animations.
    // If animation won't loop, it should stop at totalFrames - 1. If it will loop it should complete the last frame and then loop.
    if (nextValue >= this.totalFrames - 1 && this.frameModifier > 0) {
      if (!this.loop || this.playCount === this.loop) {
        if (!this.checkSegments(nextValue > this.totalFrames ? nextValue % this.totalFrames : 0)) {
          _isComplete = true
          nextValue = this.totalFrames - 1
        }
      } else if (nextValue >= this.totalFrames) {
        this.playCount += 1
        if (!this.checkSegments(nextValue % this.totalFrames)) {
          this.setCurrentRawFrameValue(nextValue % this.totalFrames)
          this._completedLoop = true
          this.trigger('loopComplete')
        }
      } else {
        this.setCurrentRawFrameValue(nextValue)
      }
    } else if (nextValue < 0) {
      if (!this.checkSegments(nextValue % this.totalFrames)) {
        if (this.loop && !(this.playCount-- <= 0 && this.loop !== true)) {
          // eslint-disable-line no-plusplus
          this.setCurrentRawFrameValue(this.totalFrames + (nextValue % this.totalFrames))
          if (!this._completedLoop) {
            this._completedLoop = true
          } else {
            this.trigger('loopComplete')
          }
        } else {
          _isComplete = true
          nextValue = 0
        }
      }
    } else {
      this.setCurrentRawFrameValue(nextValue)
    }
    if (_isComplete) {
      this.setCurrentRawFrameValue(nextValue)
      this.pause()
      this.trigger('complete')
    }
  }

  AnimationItem.prototype.adjustSegment = function (arr, offset) {
    this.playCount = 0
    if (arr[1] < arr[0]) {
      if (this.frameModifier > 0) {
        if (this.playSpeed < 0) {
          this.setSpeed(-this.playSpeed)
        } else {
          this.setDirection(-1)
        }
      }
      this.totalFrames = arr[0] - arr[1]
      this.timeCompleted = this.totalFrames
      this.firstFrame = arr[1]
      this.setCurrentRawFrameValue(this.totalFrames - 0.001 - offset)
    } else if (arr[1] > arr[0]) {
      if (this.frameModifier < 0) {
        if (this.playSpeed < 0) {
          this.setSpeed(-this.playSpeed)
        } else {
          this.setDirection(1)
        }
      }
      this.totalFrames = arr[1] - arr[0]
      this.timeCompleted = this.totalFrames
      this.firstFrame = arr[0]
      this.setCurrentRawFrameValue(0.001 + offset)
    }
    this.trigger('segmentStart')
  }
  AnimationItem.prototype.setSegment = function (init, end) {
    var pendingFrame = -1
    if (this.isPaused) {
      if (this.currentRawFrame + this.firstFrame < init) {
        pendingFrame = init
      } else if (this.currentRawFrame + this.firstFrame > end) {
        pendingFrame = end - init
      }
    }

    this.firstFrame = init
    this.totalFrames = end - init
    this.timeCompleted = this.totalFrames
    if (pendingFrame !== -1) {
      this.goToAndStop(pendingFrame, true)
    }
  }

  AnimationItem.prototype.playSegments = function (arr, forceFlag) {
    if (forceFlag) {
      this.segments.length = 0
    }
    if (typeof arr[0] === 'object') {
      var i
      var len = arr.length
      for (i = 0; i < len; i += 1) {
        this.segments.push(arr[i])
      }
    } else {
      this.segments.push(arr)
    }
    if (this.segments.length && forceFlag) {
      this.adjustSegment(this.segments.shift(), 0)
    }
    if (this.isPaused) {
      this.play()
    }
  }

  AnimationItem.prototype.resetSegments = function (forceFlag) {
    this.segments.length = 0
    this.segments.push([this.animationData.ip, this.animationData.op])
    if (forceFlag) {
      this.checkSegments(0)
    }
  }
  AnimationItem.prototype.checkSegments = function (offset) {
    if (this.segments.length) {
      this.adjustSegment(this.segments.shift(), offset)
      return true
    }
    return false
  }

  AnimationItem.prototype.destroy = function (name) {
    if ((name && this.name !== name) || !this.renderer) {
      return
    }
    this.renderer.destroy()
    this.imagePreloader.destroy()
    this.trigger('destroy')
    this._cbs = null
    this.onEnterFrame = null
    this.onLoopComplete = null
    this.onComplete = null
    this.onSegmentStart = null
    this.onDestroy = null
    this.renderer = null
    this.renderer = null
    this.imagePreloader = null
    this.projectInterface = null
  }

  AnimationItem.prototype.setCurrentRawFrameValue = function (value) {
    this.currentRawFrame = value
    this.gotoFrame()
  }

  AnimationItem.prototype.setSpeed = function (val) {
    this.playSpeed = val
    this.updaFrameModifier()
  }

  AnimationItem.prototype.setDirection = function (val) {
    this.playDirection = val < 0 ? -1 : 1
    this.updaFrameModifier()
  }

  AnimationItem.prototype.setVolume = function (val, name) {
    if (name && this.name !== name) {
      return
    }
    this.audioController.setVolume(val)
  }

  AnimationItem.prototype.getVolume = function () {
    return this.audioController.getVolume()
  }

  AnimationItem.prototype.mute = function (name) {
    if (name && this.name !== name) {
      return
    }
    this.audioController.mute()
  }

  AnimationItem.prototype.unmute = function (name) {
    if (name && this.name !== name) {
      return
    }
    this.audioController.unmute()
  }

  AnimationItem.prototype.updaFrameModifier = function () {
    this.frameModifier = this.frameMult * this.playSpeed * this.playDirection
    this.audioController.setRate(this.playSpeed * this.playDirection)
  }

  AnimationItem.prototype.getPath = function () {
    return this.path
  }

  AnimationItem.prototype.getAssetsPath = function (assetData) {
    var path = ''
    if (assetData.e) {
      path = assetData.p
    } else if (this.assetsPath) {
      var imagePath = assetData.p
      if (imagePath.indexOf('images/') !== -1) {
        imagePath = imagePath.split('/')[1]
      }
      path = this.assetsPath + imagePath
    } else {
      path = this.path
      path += assetData.u ? assetData.u : ''
      path += assetData.p
    }
    return path
  }

  AnimationItem.prototype.getAssetData = function (id) {
    var i = 0
    var len = this.assets.length
    while (i < len) {
      if (id === this.assets[i].id) {
        return this.assets[i]
      }
      i += 1
    }
    return null
  }

  AnimationItem.prototype.hide = function () {
    this.renderer.hide()
  }

  AnimationItem.prototype.show = function () {
    this.renderer.show()
  }

  AnimationItem.prototype.getDuration = function (isFrame) {
    return isFrame ? this.totalFrames : this.totalFrames / this.frameRate
  }

  AnimationItem.prototype.updateDocumentData = function (path, documentData, index) {
    try {
      var element = this.renderer.getElementByPath(path)
      element.updateDocumentData(documentData, index)
    } catch (error) {
      // TODO: decide how to handle catch case
    }
  }

  AnimationItem.prototype.trigger = function (name) {
    if (this._cbs && this._cbs[name]) {
      switch (name) {
        case 'enterFrame':
          this.triggerEvent(name, new BMEnterFrameEvent(name, this.currentFrame, this.totalFrames, this.frameModifier))
          break
        case 'drawnFrame':
          this.drawnFrameEvent.currentTime = this.currentFrame
          this.drawnFrameEvent.totalTime = this.totalFrames
          this.drawnFrameEvent.direction = this.frameModifier
          this.triggerEvent(name, this.drawnFrameEvent)
          break
        case 'loopComplete':
          this.triggerEvent(name, new BMCompleteLoopEvent(name, this.loop, this.playCount, this.frameMult))
          break
        case 'complete':
          this.triggerEvent(name, new BMCompleteEvent(name, this.frameMult))
          break
        case 'segmentStart':
          this.triggerEvent(name, new BMSegmentStartEvent(name, this.firstFrame, this.totalFrames))
          break
        case 'destroy':
          this.triggerEvent(name, new BMDestroyEvent(name, this))
          break
        default:
          this.triggerEvent(name)
      }
    }
    if (name === 'enterFrame' && this.onEnterFrame) {
      this.onEnterFrame.call(this, new BMEnterFrameEvent(name, this.currentFrame, this.totalFrames, this.frameMult))
    }
    if (name === 'loopComplete' && this.onLoopComplete) {
      this.onLoopComplete.call(this, new BMCompleteLoopEvent(name, this.loop, this.playCount, this.frameMult))
    }
    if (name === 'complete' && this.onComplete) {
      this.onComplete.call(this, new BMCompleteEvent(name, this.frameMult))
    }
    if (name === 'segmentStart' && this.onSegmentStart) {
      this.onSegmentStart.call(this, new BMSegmentStartEvent(name, this.firstFrame, this.totalFrames))
    }
    if (name === 'destroy' && this.onDestroy) {
      this.onDestroy.call(this, new BMDestroyEvent(name, this))
    }
  }

  AnimationItem.prototype.triggerRenderFrameError = function (nativeError) {
    var error = new BMRenderFrameErrorEvent(nativeError, this.currentFrame)
    this.triggerEvent('error', error)

    if (this.onError) {
      this.onError.call(this, error)
    }
  }

  AnimationItem.prototype.triggerConfigError = function (nativeError) {
    var error = new BMConfigErrorEvent(nativeError, this.currentFrame)
    this.triggerEvent('error', error)

    if (this.onError) {
      this.onError.call(this, error)
    }
  }

  const animationManager = (function () {
    var moduleOb = {}
    var registeredAnimations = []
    var initTime = 0
    var len = 0
    var playingAnimationsNum = 0
    var _stopped = true
    var _isFrozen = false

    function removeElement(ev) {
      var i = 0
      var animItem = ev.target
      while (i < len) {
        if (registeredAnimations[i].animation === animItem) {
          registeredAnimations.splice(i, 1)
          i -= 1
          len -= 1
          if (!animItem.isPaused) {
            subtractPlayingCount()
          }
        }
        i += 1
      }
    }

    function registerAnimation(element, animationData) {
      if (!element) {
        return null
      }
      var i = 0
      while (i < len) {
        if (registeredAnimations[i].elem === element && registeredAnimations[i].elem !== null) {
          return registeredAnimations[i].animation
        }
        i += 1
      }
      var animItem = new AnimationItem()
      setupAnimation(animItem, element)
      animItem.setData(element, animationData)
      return animItem
    }

    function getRegisteredAnimations() {
      var i
      var lenAnims = registeredAnimations.length
      var animations = []
      for (i = 0; i < lenAnims; i += 1) {
        animations.push(registeredAnimations[i].animation)
      }
      return animations
    }

    function addPlayingCount() {
      playingAnimationsNum += 1
      activate()
    }

    function subtractPlayingCount() {
      playingAnimationsNum -= 1
    }

    function setupAnimation(animItem, element) {
      animItem.addEventListener('destroy', removeElement)
      animItem.addEventListener('_active', addPlayingCount)
      animItem.addEventListener('_idle', subtractPlayingCount)
      registeredAnimations.push({ elem: element, animation: animItem })
      len += 1
    }

    function loadAnimation(params) {
      var animItem = new AnimationItem()
      setupAnimation(animItem, null)
      animItem.setParams(params)
      return animItem
    }

    function setSpeed(val, animation) {
      var i
      for (i = 0; i < len; i += 1) {
        registeredAnimations[i].animation.setSpeed(val, animation)
      }
    }

    function setDirection(val, animation) {
      var i
      for (i = 0; i < len; i += 1) {
        registeredAnimations[i].animation.setDirection(val, animation)
      }
    }

    function play(animation) {
      var i
      for (i = 0; i < len; i += 1) {
        registeredAnimations[i].animation.play(animation)
      }
    }
    function resume(nowTime) {
      var elapsedTime = nowTime - initTime
      var i
      for (i = 0; i < len; i += 1) {
        registeredAnimations[i].animation.advanceTime(elapsedTime)
      }
      initTime = nowTime
      if (playingAnimationsNum && !_isFrozen) {
        window.requestAnimationFrame(resume)
      } else {
        _stopped = true
      }
    }

    function first(nowTime) {
      initTime = nowTime
      window.requestAnimationFrame(resume)
    }

    function pause(animation) {
      var i
      for (i = 0; i < len; i += 1) {
        registeredAnimations[i].animation.pause(animation)
      }
    }

    function goToAndStop(value, isFrame, animation) {
      var i
      for (i = 0; i < len; i += 1) {
        registeredAnimations[i].animation.goToAndStop(value, isFrame, animation)
      }
    }

    function stop(animation) {
      var i
      for (i = 0; i < len; i += 1) {
        registeredAnimations[i].animation.stop(animation)
      }
    }

    function togglePause(animation) {
      var i
      for (i = 0; i < len; i += 1) {
        registeredAnimations[i].animation.togglePause(animation)
      }
    }

    function destroy(animation) {
      var i
      for (i = len - 1; i >= 0; i -= 1) {
        registeredAnimations[i].animation.destroy(animation)
      }
    }

    function searchAnimations(animationData, standalone, renderer) {
      var animElements = [].concat(
        [].slice.call(document.getElementsByClassName('lottie')),
        [].slice.call(document.getElementsByClassName('bodymovin')),
      )
      var i
      var lenAnims = animElements.length
      for (i = 0; i < lenAnims; i += 1) {
        if (renderer) {
          animElements[i].setAttribute('data-bm-type', renderer)
        }
        registerAnimation(animElements[i], animationData)
      }
      if (standalone && lenAnims === 0) {
        if (!renderer) {
          renderer = 'svg'
        }
        var body = document.getElementsByTagName('body')[0]
        body.innerText = ''
        var div = createTag('div')
        div.style.width = '100%'
        div.style.height = '100%'
        div.setAttribute('data-bm-type', renderer)
        body.appendChild(div)
        registerAnimation(div, animationData)
      }
    }

    function resize() {
      var i
      for (i = 0; i < len; i += 1) {
        registeredAnimations[i].animation.resize()
      }
    }

    function activate() {
      if (!_isFrozen && playingAnimationsNum) {
        if (_stopped) {
          window.requestAnimationFrame(first)
          _stopped = false
        }
      }
    }

    function freeze() {
      _isFrozen = true
    }

    function unfreeze() {
      _isFrozen = false
      activate()
    }

    function setVolume(val, animation) {
      var i
      for (i = 0; i < len; i += 1) {
        registeredAnimations[i].animation.setVolume(val, animation)
      }
    }

    function mute(animation) {
      var i
      for (i = 0; i < len; i += 1) {
        registeredAnimations[i].animation.mute(animation)
      }
    }

    function unmute(animation) {
      var i
      for (i = 0; i < len; i += 1) {
        registeredAnimations[i].animation.unmute(animation)
      }
    }

    moduleOb.registerAnimation = registerAnimation
    moduleOb.loadAnimation = loadAnimation
    moduleOb.setSpeed = setSpeed
    moduleOb.setDirection = setDirection
    moduleOb.play = play
    moduleOb.pause = pause
    moduleOb.stop = stop
    moduleOb.togglePause = togglePause
    moduleOb.searchAnimations = searchAnimations
    moduleOb.resize = resize
    // moduleOb.start = start;
    moduleOb.goToAndStop = goToAndStop
    moduleOb.destroy = destroy
    moduleOb.freeze = freeze
    moduleOb.unfreeze = unfreeze
    moduleOb.setVolume = setVolume
    moduleOb.mute = mute
    moduleOb.unmute = unmute
    moduleOb.getRegisteredAnimations = getRegisteredAnimations
    return moduleOb
  })()

  /* eslint-disable */
  const BezierFactory = (function () {
    /**
     * BezierEasing - use bezier curve for transition easing function
     * by Gaëtan Renaudeau 2014 - 2015 – MIT License
     *
     * Credits: is based on Firefox's nsSMILKeySpline.cpp
     * Usage:
     * var spline = BezierEasing([ 0.25, 0.1, 0.25, 1.0 ])
     * spline.get(x) => returns the easing value | x must be in [0, 1] range
     *
     */

    var ob = {}
    ob.getBezierEasing = getBezierEasing
    var beziers = {}

    function getBezierEasing(a, b, c, d, nm) {
      var str = nm || ('bez_' + a + '_' + b + '_' + c + '_' + d).replace(/\./g, 'p')
      if (beziers[str]) {
        return beziers[str]
      }
      var bezEasing = new BezierEasing([a, b, c, d])
      beziers[str] = bezEasing
      return bezEasing
    }

    // These values are established by empiricism with tests (tradeoff: performance VS precision)
    var NEWTON_ITERATIONS = 4
    var NEWTON_MIN_SLOPE = 0.001
    var SUBDIVISION_PRECISION = 0.0000001
    var SUBDIVISION_MAX_ITERATIONS = 10

    var kSplineTableSize = 11
    var kSampleStepSize = 1.0 / (kSplineTableSize - 1.0)

    var float32ArraySupported = typeof Float32Array === 'function'

    function A(aA1, aA2) {
      return 1.0 - 3.0 * aA2 + 3.0 * aA1
    }
    function B(aA1, aA2) {
      return 3.0 * aA2 - 6.0 * aA1
    }
    function C(aA1) {
      return 3.0 * aA1
    }

    // Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
    function calcBezier(aT, aA1, aA2) {
      return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT
    }

    // Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2.
    function getSlope(aT, aA1, aA2) {
      return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1)
    }

    function binarySubdivide(aX, aA, aB, mX1, mX2) {
      var currentX,
        currentT,
        i = 0
      do {
        currentT = aA + (aB - aA) / 2.0
        currentX = calcBezier(currentT, mX1, mX2) - aX
        if (currentX > 0.0) {
          aB = currentT
        } else {
          aA = currentT
        }
      } while (Math.abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS)
      return currentT
    }

    function newtonRaphsonIterate(aX, aGuessT, mX1, mX2) {
      for (var i = 0; i < NEWTON_ITERATIONS; ++i) {
        var currentSlope = getSlope(aGuessT, mX1, mX2)
        if (currentSlope === 0.0) return aGuessT
        var currentX = calcBezier(aGuessT, mX1, mX2) - aX
        aGuessT -= currentX / currentSlope
      }
      return aGuessT
    }

    /**
     * points is an array of [ mX1, mY1, mX2, mY2 ]
     */
    function BezierEasing(points) {
      this._p = points
      this._mSampleValues = float32ArraySupported ? new Float32Array(kSplineTableSize) : new Array(kSplineTableSize)
      this._precomputed = false

      this.get = this.get.bind(this)
    }

    BezierEasing.prototype = {
      get: function (x) {
        var mX1 = this._p[0],
          mY1 = this._p[1],
          mX2 = this._p[2],
          mY2 = this._p[3]
        if (!this._precomputed) this._precompute()
        if (mX1 === mY1 && mX2 === mY2) return x // linear
        // Because JavaScript number are imprecise, we should guarantee the extremes are right.
        if (x === 0) return 0
        if (x === 1) return 1
        return calcBezier(this._getTForX(x), mY1, mY2)
      },

      // Private part

      _precompute: function () {
        var mX1 = this._p[0],
          mY1 = this._p[1],
          mX2 = this._p[2],
          mY2 = this._p[3]
        this._precomputed = true
        if (mX1 !== mY1 || mX2 !== mY2) {
          this._calcSampleValues()
        }
      },

      _calcSampleValues: function () {
        var mX1 = this._p[0],
          mX2 = this._p[2]
        for (var i = 0; i < kSplineTableSize; ++i) {
          this._mSampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2)
        }
      },

      /**
       * getTForX chose the fastest heuristic to determine the percentage value precisely from a given X projection.
       */
      _getTForX: function (aX) {
        var mX1 = this._p[0],
          mX2 = this._p[2],
          mSampleValues = this._mSampleValues

        var intervalStart = 0.0
        var currentSample = 1
        var lastSample = kSplineTableSize - 1

        for (; currentSample !== lastSample && mSampleValues[currentSample] <= aX; ++currentSample) {
          intervalStart += kSampleStepSize
        }
        --currentSample

        // Interpolate to provide an initial guess for t
        var dist =
          (aX - mSampleValues[currentSample]) / (mSampleValues[currentSample + 1] - mSampleValues[currentSample])
        var guessForT = intervalStart + dist * kSampleStepSize

        var initialSlope = getSlope(guessForT, mX1, mX2)
        if (initialSlope >= NEWTON_MIN_SLOPE) {
          return newtonRaphsonIterate(aX, guessForT, mX1, mX2)
        }
        if (initialSlope === 0.0) {
          return guessForT
        }
        return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize, mX1, mX2)
      },
    }

    return ob
  })()

  const pooling = (function () {
    function double(arr) {
      return arr.concat(createSizedArray(arr.length))
    }

    return {
      double: double,
    }
  })()

  const poolFactory = (function () {
    return function (initialLength, _create, _release) {
      var _length = 0
      var _maxLength = initialLength
      var pool = createSizedArray(_maxLength)

      var ob = {
        newElement: newElement,
        release: release,
      }

      function newElement() {
        var element
        if (_length) {
          _length -= 1
          element = pool[_length]
        } else {
          element = _create()
        }
        return element
      }

      function release(element) {
        if (_length === _maxLength) {
          pool = pooling.double(pool)
          _maxLength *= 2
        }
        if (_release) {
          _release(element)
        }
        pool[_length] = element
        _length += 1
      }

      return ob
    }
  })()

  const bezierLengthPool = (function () {
    function create() {
      return {
        addedLength: 0,
        percents: createTypedArray('float32', getDefaultCurveSegments()),
        lengths: createTypedArray('float32', getDefaultCurveSegments()),
      }
    }
    return poolFactory(8, create)
  })()

  const segmentsLengthPool = (function () {
    function create() {
      return {
        lengths: [],
        totalLength: 0,
      }
    }

    function release(element) {
      var i
      var len = element.lengths.length
      for (i = 0; i < len; i += 1) {
        bezierLengthPool.release(element.lengths[i])
      }
      element.lengths.length = 0
    }

    return poolFactory(8, create, release)
  })()

  function bezFunction() {
    var math = Math

    function pointOnLine2D(x1, y1, x2, y2, x3, y3) {
      var det1 = x1 * y2 + y1 * x3 + x2 * y3 - x3 * y2 - y3 * x1 - x2 * y1
      return det1 > -0.001 && det1 < 0.001
    }

    function pointOnLine3D(x1, y1, z1, x2, y2, z2, x3, y3, z3) {
      if (z1 === 0 && z2 === 0 && z3 === 0) {
        return pointOnLine2D(x1, y1, x2, y2, x3, y3)
      }
      var dist1 = math.sqrt(math.pow(x2 - x1, 2) + math.pow(y2 - y1, 2) + math.pow(z2 - z1, 2))
      var dist2 = math.sqrt(math.pow(x3 - x1, 2) + math.pow(y3 - y1, 2) + math.pow(z3 - z1, 2))
      var dist3 = math.sqrt(math.pow(x3 - x2, 2) + math.pow(y3 - y2, 2) + math.pow(z3 - z2, 2))
      var diffDist
      if (dist1 > dist2) {
        if (dist1 > dist3) {
          diffDist = dist1 - dist2 - dist3
        } else {
          diffDist = dist3 - dist2 - dist1
        }
      } else if (dist3 > dist2) {
        diffDist = dist3 - dist2 - dist1
      } else {
        diffDist = dist2 - dist1 - dist3
      }
      return diffDist > -0.0001 && diffDist < 0.0001
    }

    var getBezierLength = (function () {
      return function (pt1, pt2, pt3, pt4) {
        var curveSegments = getDefaultCurveSegments()
        var k
        var i
        var len
        var ptCoord
        var perc
        var addedLength = 0
        var ptDistance
        var point = []
        var lastPoint = []
        var lengthData = bezierLengthPool.newElement()
        len = pt3.length
        for (k = 0; k < curveSegments; k += 1) {
          perc = k / (curveSegments - 1)
          ptDistance = 0
          for (i = 0; i < len; i += 1) {
            ptCoord =
              bmPow(1 - perc, 3) * pt1[i] +
              3 * bmPow(1 - perc, 2) * perc * pt3[i] +
              3 * (1 - perc) * bmPow(perc, 2) * pt4[i] +
              bmPow(perc, 3) * pt2[i]
            point[i] = ptCoord
            if (lastPoint[i] !== null) {
              ptDistance += bmPow(point[i] - lastPoint[i], 2)
            }
            lastPoint[i] = point[i]
          }
          if (ptDistance) {
            ptDistance = bmSqrt(ptDistance)
            addedLength += ptDistance
          }
          lengthData.percents[k] = perc
          lengthData.lengths[k] = addedLength
        }
        lengthData.addedLength = addedLength
        return lengthData
      }
    })()

    function getSegmentsLength(shapeData) {
      var segmentsLength = segmentsLengthPool.newElement()
      var closed = shapeData.c
      var pathV = shapeData.v
      var pathO = shapeData.o
      var pathI = shapeData.i
      var i
      var len = shapeData._length
      var lengths = segmentsLength.lengths
      var totalLength = 0
      for (i = 0; i < len - 1; i += 1) {
        lengths[i] = getBezierLength(pathV[i], pathV[i + 1], pathO[i], pathI[i + 1])
        totalLength += lengths[i].addedLength
      }
      if (closed && len) {
        lengths[i] = getBezierLength(pathV[i], pathV[0], pathO[i], pathI[0])
        totalLength += lengths[i].addedLength
      }
      segmentsLength.totalLength = totalLength
      return segmentsLength
    }

    function BezierData(length) {
      this.segmentLength = 0
      this.points = new Array(length)
    }

    function PointData(partial, point) {
      this.partialLength = partial
      this.point = point
    }

    var buildBezierData = (function () {
      var storedData = {}

      return function (pt1, pt2, pt3, pt4) {
        var bezierName = (
          pt1[0] +
          '_' +
          pt1[1] +
          '_' +
          pt2[0] +
          '_' +
          pt2[1] +
          '_' +
          pt3[0] +
          '_' +
          pt3[1] +
          '_' +
          pt4[0] +
          '_' +
          pt4[1]
        ).replace(/\./g, 'p')
        if (!storedData[bezierName]) {
          var curveSegments = getDefaultCurveSegments()
          var k
          var i
          var len
          var ptCoord
          var perc
          var addedLength = 0
          var ptDistance
          var point
          var lastPoint = null
          if (
            pt1.length === 2 &&
            (pt1[0] !== pt2[0] || pt1[1] !== pt2[1]) &&
            pointOnLine2D(pt1[0], pt1[1], pt2[0], pt2[1], pt1[0] + pt3[0], pt1[1] + pt3[1]) &&
            pointOnLine2D(pt1[0], pt1[1], pt2[0], pt2[1], pt2[0] + pt4[0], pt2[1] + pt4[1])
          ) {
            curveSegments = 2
          }
          var bezierData = new BezierData(curveSegments)
          len = pt3.length
          for (k = 0; k < curveSegments; k += 1) {
            point = createSizedArray(len)
            perc = k / (curveSegments - 1)
            ptDistance = 0
            for (i = 0; i < len; i += 1) {
              ptCoord =
                bmPow(1 - perc, 3) * pt1[i] +
                3 * bmPow(1 - perc, 2) * perc * (pt1[i] + pt3[i]) +
                3 * (1 - perc) * bmPow(perc, 2) * (pt2[i] + pt4[i]) +
                bmPow(perc, 3) * pt2[i]
              point[i] = ptCoord
              if (lastPoint !== null) {
                ptDistance += bmPow(point[i] - lastPoint[i], 2)
              }
            }
            ptDistance = bmSqrt(ptDistance)
            addedLength += ptDistance
            bezierData.points[k] = new PointData(ptDistance, point)
            lastPoint = point
          }
          bezierData.segmentLength = addedLength
          storedData[bezierName] = bezierData
        }
        return storedData[bezierName]
      }
    })()

    function getDistancePerc(perc, bezierData) {
      var percents = bezierData.percents
      var lengths = bezierData.lengths
      var len = percents.length
      var initPos = bmFloor((len - 1) * perc)
      var lengthPos = perc * bezierData.addedLength
      var lPerc = 0
      if (initPos === len - 1 || initPos === 0 || lengthPos === lengths[initPos]) {
        return percents[initPos]
      }
      var dir = lengths[initPos] > lengthPos ? -1 : 1
      var flag = true
      while (flag) {
        if (lengths[initPos] <= lengthPos && lengths[initPos + 1] > lengthPos) {
          lPerc = (lengthPos - lengths[initPos]) / (lengths[initPos + 1] - lengths[initPos])
          flag = false
        } else {
          initPos += dir
        }
        if (initPos < 0 || initPos >= len - 1) {
          // FIX for TypedArrays that don't store floating point values with enough accuracy
          if (initPos === len - 1) {
            return percents[initPos]
          }
          flag = false
        }
      }
      return percents[initPos] + (percents[initPos + 1] - percents[initPos]) * lPerc
    }

    function getPointInSegment(pt1, pt2, pt3, pt4, percent, bezierData) {
      var t1 = getDistancePerc(percent, bezierData)
      var u1 = 1 - t1
      var ptX =
        math.round(
          (u1 * u1 * u1 * pt1[0] +
            (t1 * u1 * u1 + u1 * t1 * u1 + u1 * u1 * t1) * pt3[0] +
            (t1 * t1 * u1 + u1 * t1 * t1 + t1 * u1 * t1) * pt4[0] +
            t1 * t1 * t1 * pt2[0]) *
            1000,
        ) / 1000
      var ptY =
        math.round(
          (u1 * u1 * u1 * pt1[1] +
            (t1 * u1 * u1 + u1 * t1 * u1 + u1 * u1 * t1) * pt3[1] +
            (t1 * t1 * u1 + u1 * t1 * t1 + t1 * u1 * t1) * pt4[1] +
            t1 * t1 * t1 * pt2[1]) *
            1000,
        ) / 1000
      return [ptX, ptY]
    }

    var bezierSegmentPoints = createTypedArray('float32', 8)

    function getNewSegment(pt1, pt2, pt3, pt4, startPerc, endPerc, bezierData) {
      if (startPerc < 0) {
        startPerc = 0
      } else if (startPerc > 1) {
        startPerc = 1
      }
      var t0 = getDistancePerc(startPerc, bezierData)
      endPerc = endPerc > 1 ? 1 : endPerc
      var t1 = getDistancePerc(endPerc, bezierData)
      var i
      var len = pt1.length
      var u0 = 1 - t0
      var u1 = 1 - t1
      var u0u0u0 = u0 * u0 * u0
      var t0u0u0_3 = t0 * u0 * u0 * 3 // eslint-disable-line camelcase
      var t0t0u0_3 = t0 * t0 * u0 * 3 // eslint-disable-line camelcase
      var t0t0t0 = t0 * t0 * t0
      //
      var u0u0u1 = u0 * u0 * u1
      var t0u0u1_3 = t0 * u0 * u1 + u0 * t0 * u1 + u0 * u0 * t1 // eslint-disable-line camelcase
      var t0t0u1_3 = t0 * t0 * u1 + u0 * t0 * t1 + t0 * u0 * t1 // eslint-disable-line camelcase
      var t0t0t1 = t0 * t0 * t1
      //
      var u0u1u1 = u0 * u1 * u1
      var t0u1u1_3 = t0 * u1 * u1 + u0 * t1 * u1 + u0 * u1 * t1 // eslint-disable-line camelcase
      var t0t1u1_3 = t0 * t1 * u1 + u0 * t1 * t1 + t0 * u1 * t1 // eslint-disable-line camelcase
      var t0t1t1 = t0 * t1 * t1
      //
      var u1u1u1 = u1 * u1 * u1
      var t1u1u1_3 = t1 * u1 * u1 + u1 * t1 * u1 + u1 * u1 * t1 // eslint-disable-line camelcase
      var t1t1u1_3 = t1 * t1 * u1 + u1 * t1 * t1 + t1 * u1 * t1 // eslint-disable-line camelcase
      var t1t1t1 = t1 * t1 * t1
      for (i = 0; i < len; i += 1) {
        bezierSegmentPoints[i * 4] =
          math.round((u0u0u0 * pt1[i] + t0u0u0_3 * pt3[i] + t0t0u0_3 * pt4[i] + t0t0t0 * pt2[i]) * 1000) / 1000 // eslint-disable-line camelcase
        bezierSegmentPoints[i * 4 + 1] =
          math.round((u0u0u1 * pt1[i] + t0u0u1_3 * pt3[i] + t0t0u1_3 * pt4[i] + t0t0t1 * pt2[i]) * 1000) / 1000 // eslint-disable-line camelcase
        bezierSegmentPoints[i * 4 + 2] =
          math.round((u0u1u1 * pt1[i] + t0u1u1_3 * pt3[i] + t0t1u1_3 * pt4[i] + t0t1t1 * pt2[i]) * 1000) / 1000 // eslint-disable-line camelcase
        bezierSegmentPoints[i * 4 + 3] =
          math.round((u1u1u1 * pt1[i] + t1u1u1_3 * pt3[i] + t1t1u1_3 * pt4[i] + t1t1t1 * pt2[i]) * 1000) / 1000 // eslint-disable-line camelcase
      }

      return bezierSegmentPoints
    }

    return {
      getSegmentsLength: getSegmentsLength,
      getNewSegment: getNewSegment,
      getPointInSegment: getPointInSegment,
      buildBezierData: buildBezierData,
      pointOnLine2D: pointOnLine2D,
      pointOnLine3D: pointOnLine3D,
    }
  }

  const bez = bezFunction()

  const PropertyFactory = (function () {
    var initFrame = initialDefaultFrame
    var mathAbs = Math.abs

    function interpolateValue(frameNum, caching) {
      var offsetTime = this.offsetTime
      var newValue
      if (this.propType === 'multidimensional') {
        newValue = createTypedArray('float32', this.pv.length)
      }
      var iterationIndex = caching.lastIndex
      var i = iterationIndex
      var len = this.keyframes.length - 1
      var flag = true
      var keyData
      var nextKeyData
      var keyframeMetadata

      while (flag) {
        keyData = this.keyframes[i]
        nextKeyData = this.keyframes[i + 1]
        if (i === len - 1 && frameNum >= nextKeyData.t - offsetTime) {
          if (keyData.h) {
            keyData = nextKeyData
          }
          iterationIndex = 0
          break
        }
        if (nextKeyData.t - offsetTime > frameNum) {
          iterationIndex = i
          break
        }
        if (i < len - 1) {
          i += 1
        } else {
          iterationIndex = 0
          flag = false
        }
      }
      keyframeMetadata = this.keyframesMetadata[i] || {}

      var k
      var kLen
      var perc
      var jLen
      var j
      var fnc
      var nextKeyTime = nextKeyData.t - offsetTime
      var keyTime = keyData.t - offsetTime
      var endValue
      if (keyData.to) {
        if (!keyframeMetadata.bezierData) {
          keyframeMetadata.bezierData = bez.buildBezierData(
            keyData.s,
            nextKeyData.s || keyData.e,
            keyData.to,
            keyData.ti,
          )
        }
        var bezierData = keyframeMetadata.bezierData
        if (frameNum >= nextKeyTime || frameNum < keyTime) {
          var ind = frameNum >= nextKeyTime ? bezierData.points.length - 1 : 0
          kLen = bezierData.points[ind].point.length
          for (k = 0; k < kLen; k += 1) {
            newValue[k] = bezierData.points[ind].point[k]
          }
          // caching._lastKeyframeIndex = -1;
        } else {
          if (keyframeMetadata.__fnct) {
            fnc = keyframeMetadata.__fnct
          } else {
            fnc = BezierFactory.getBezierEasing(keyData.o.x, keyData.o.y, keyData.i.x, keyData.i.y, keyData.n).get
            keyframeMetadata.__fnct = fnc
          }
          perc = fnc((frameNum - keyTime) / (nextKeyTime - keyTime))
          var distanceInLine = bezierData.segmentLength * perc

          var segmentPerc
          var addedLength =
            caching.lastFrame < frameNum && caching._lastKeyframeIndex === i ? caching._lastAddedLength : 0
          j = caching.lastFrame < frameNum && caching._lastKeyframeIndex === i ? caching._lastPoint : 0
          flag = true
          jLen = bezierData.points.length
          while (flag) {
            addedLength += bezierData.points[j].partialLength
            if (distanceInLine === 0 || perc === 0 || j === bezierData.points.length - 1) {
              kLen = bezierData.points[j].point.length
              for (k = 0; k < kLen; k += 1) {
                newValue[k] = bezierData.points[j].point[k]
              }
              break
            } else if (
              distanceInLine >= addedLength &&
              distanceInLine < addedLength + bezierData.points[j + 1].partialLength
            ) {
              segmentPerc = (distanceInLine - addedLength) / bezierData.points[j + 1].partialLength
              kLen = bezierData.points[j].point.length
              for (k = 0; k < kLen; k += 1) {
                newValue[k] =
                  bezierData.points[j].point[k] +
                  (bezierData.points[j + 1].point[k] - bezierData.points[j].point[k]) * segmentPerc
              }
              break
            }
            if (j < jLen - 1) {
              j += 1
            } else {
              flag = false
            }
          }
          caching._lastPoint = j
          caching._lastAddedLength = addedLength - bezierData.points[j].partialLength
          caching._lastKeyframeIndex = i
        }
      } else {
        var outX
        var outY
        var inX
        var inY
        var keyValue
        len = keyData.s.length
        endValue = nextKeyData.s || keyData.e
        if (this.sh && keyData.h !== 1) {
          if (frameNum >= nextKeyTime) {
            newValue[0] = endValue[0]
            newValue[1] = endValue[1]
            newValue[2] = endValue[2]
          } else if (frameNum <= keyTime) {
            newValue[0] = keyData.s[0]
            newValue[1] = keyData.s[1]
            newValue[2] = keyData.s[2]
          } else {
            var quatStart = createQuaternion(keyData.s)
            var quatEnd = createQuaternion(endValue)
            var time = (frameNum - keyTime) / (nextKeyTime - keyTime)
            quaternionToEuler(newValue, slerp(quatStart, quatEnd, time))
          }
        } else {
          for (i = 0; i < len; i += 1) {
            if (keyData.h !== 1) {
              if (frameNum >= nextKeyTime) {
                perc = 1
              } else if (frameNum < keyTime) {
                perc = 0
              } else {
                if (keyData.o.x.constructor === Array) {
                  if (!keyframeMetadata.__fnct) {
                    keyframeMetadata.__fnct = []
                  }
                  if (!keyframeMetadata.__fnct[i]) {
                    outX = keyData.o.x[i] === undefined ? keyData.o.x[0] : keyData.o.x[i]
                    outY = keyData.o.y[i] === undefined ? keyData.o.y[0] : keyData.o.y[i]
                    inX = keyData.i.x[i] === undefined ? keyData.i.x[0] : keyData.i.x[i]
                    inY = keyData.i.y[i] === undefined ? keyData.i.y[0] : keyData.i.y[i]
                    fnc = BezierFactory.getBezierEasing(outX, outY, inX, inY).get
                    keyframeMetadata.__fnct[i] = fnc
                  } else {
                    fnc = keyframeMetadata.__fnct[i]
                  }
                } else if (!keyframeMetadata.__fnct) {
                  outX = keyData.o.x
                  outY = keyData.o.y
                  inX = keyData.i.x
                  inY = keyData.i.y
                  fnc = BezierFactory.getBezierEasing(outX, outY, inX, inY).get
                  keyData.keyframeMetadata = fnc
                } else {
                  fnc = keyframeMetadata.__fnct
                }
                perc = fnc((frameNum - keyTime) / (nextKeyTime - keyTime))
              }
            }

            endValue = nextKeyData.s || keyData.e
            keyValue = keyData.h === 1 ? keyData.s[i] : keyData.s[i] + (endValue[i] - keyData.s[i]) * perc

            if (this.propType === 'multidimensional') {
              newValue[i] = keyValue
            } else {
              newValue = keyValue
            }
          }
        }
      }
      caching.lastIndex = iterationIndex
      return newValue
    }

    // based on @Toji's https://github.com/toji/gl-matrix/
    function slerp(a, b, t) {
      var out = []
      var ax = a[0]
      var ay = a[1]
      var az = a[2]
      var aw = a[3]
      var bx = b[0]
      var by = b[1]
      var bz = b[2]
      var bw = b[3]

      var omega
      var cosom
      var sinom
      var scale0
      var scale1

      cosom = ax * bx + ay * by + az * bz + aw * bw
      if (cosom < 0.0) {
        cosom = -cosom
        bx = -bx
        by = -by
        bz = -bz
        bw = -bw
      }
      if (1.0 - cosom > 0.000001) {
        omega = Math.acos(cosom)
        sinom = Math.sin(omega)
        scale0 = Math.sin((1.0 - t) * omega) / sinom
        scale1 = Math.sin(t * omega) / sinom
      } else {
        scale0 = 1.0 - t
        scale1 = t
      }
      out[0] = scale0 * ax + scale1 * bx
      out[1] = scale0 * ay + scale1 * by
      out[2] = scale0 * az + scale1 * bz
      out[3] = scale0 * aw + scale1 * bw

      return out
    }

    function quaternionToEuler(out, quat) {
      var qx = quat[0]
      var qy = quat[1]
      var qz = quat[2]
      var qw = quat[3]
      var heading = Math.atan2(2 * qy * qw - 2 * qx * qz, 1 - 2 * qy * qy - 2 * qz * qz)
      var attitude = Math.asin(2 * qx * qy + 2 * qz * qw)
      var bank = Math.atan2(2 * qx * qw - 2 * qy * qz, 1 - 2 * qx * qx - 2 * qz * qz)
      out[0] = heading / degToRads
      out[1] = attitude / degToRads
      out[2] = bank / degToRads
    }

    function createQuaternion(values) {
      var heading = values[0] * degToRads
      var attitude = values[1] * degToRads
      var bank = values[2] * degToRads
      var c1 = Math.cos(heading / 2)
      var c2 = Math.cos(attitude / 2)
      var c3 = Math.cos(bank / 2)
      var s1 = Math.sin(heading / 2)
      var s2 = Math.sin(attitude / 2)
      var s3 = Math.sin(bank / 2)
      var w = c1 * c2 * c3 - s1 * s2 * s3
      var x = s1 * s2 * c3 + c1 * c2 * s3
      var y = s1 * c2 * c3 + c1 * s2 * s3
      var z = c1 * s2 * c3 - s1 * c2 * s3

      return [x, y, z, w]
    }

    function getValueAtCurrentTime() {
      var frameNum = this.comp.renderedFrame - this.offsetTime
      var initTime = this.keyframes[0].t - this.offsetTime
      var endTime = this.keyframes[this.keyframes.length - 1].t - this.offsetTime
      if (
        !(
          frameNum === this._caching.lastFrame ||
          (this._caching.lastFrame !== initFrame &&
            ((this._caching.lastFrame >= endTime && frameNum >= endTime) ||
              (this._caching.lastFrame < initTime && frameNum < initTime)))
        )
      ) {
        if (this._caching.lastFrame >= frameNum) {
          this._caching._lastKeyframeIndex = -1
          this._caching.lastIndex = 0
        }

        var renderResult = this.interpolateValue(frameNum, this._caching)
        this.pv = renderResult
      }
      this._caching.lastFrame = frameNum
      return this.pv
    }

    function setVValue(val) {
      var multipliedValue
      if (this.propType === 'unidimensional') {
        multipliedValue = val * this.mult
        if (mathAbs(this.v - multipliedValue) > 0.00001) {
          this.v = multipliedValue
          this._mdf = true
        }
      } else {
        var i = 0
        var len = this.v.length
        while (i < len) {
          multipliedValue = val[i] * this.mult
          if (mathAbs(this.v[i] - multipliedValue) > 0.00001) {
            this.v[i] = multipliedValue
            this._mdf = true
          }
          i += 1
        }
      }
    }

    function processEffectsSequence() {
      if (this.elem.globalData.frameId === this.frameId || !this.effectsSequence.length) {
        return
      }
      if (this.lock) {
        this.setVValue(this.pv)
        return
      }
      this.lock = true
      this._mdf = this._isFirstFrame
      var i
      var len = this.effectsSequence.length
      var finalValue = this.kf ? this.pv : this.data.k
      for (i = 0; i < len; i += 1) {
        finalValue = this.effectsSequence[i](finalValue)
      }
      this.setVValue(finalValue)
      this._isFirstFrame = false
      this.lock = false
      this.frameId = this.elem.globalData.frameId
    }

    function addEffect(effectFunction) {
      this.effectsSequence.push(effectFunction)
      this.container.addDynamicProperty(this)
    }

    function ValueProperty(elem, data, mult, container) {
      this.propType = 'unidimensional'
      this.mult = mult || 1
      this.data = data
      this.v = mult ? data.k * mult : data.k
      this.pv = data.k
      this._mdf = false
      this.elem = elem
      this.container = container
      this.comp = elem.comp
      this.k = false
      this.kf = false
      this.vel = 0
      this.effectsSequence = []
      this._isFirstFrame = true
      this.getValue = processEffectsSequence
      this.setVValue = setVValue
      this.addEffect = addEffect
    }

    function MultiDimensionalProperty(elem, data, mult, container) {
      this.propType = 'multidimensional'
      this.mult = mult || 1
      this.data = data
      this._mdf = false
      this.elem = elem
      this.container = container
      this.comp = elem.comp
      this.k = false
      this.kf = false
      this.frameId = -1
      var i
      var len = data.k.length
      this.v = createTypedArray('float32', len)
      this.pv = createTypedArray('float32', len)
      this.vel = createTypedArray('float32', len)
      for (i = 0; i < len; i += 1) {
        this.v[i] = data.k[i] * this.mult
        this.pv[i] = data.k[i]
      }
      this._isFirstFrame = true
      this.effectsSequence = []
      this.getValue = processEffectsSequence
      this.setVValue = setVValue
      this.addEffect = addEffect
    }

    function KeyframedValueProperty(elem, data, mult, container) {
      this.propType = 'unidimensional'
      this.keyframes = data.k
      this.keyframesMetadata = []
      this.offsetTime = elem.data.st
      this.frameId = -1
      this._caching = {
        lastFrame: initFrame,
        lastIndex: 0,
        value: 0,
        _lastKeyframeIndex: -1,
      }
      this.k = true
      this.kf = true
      this.data = data
      this.mult = mult || 1
      this.elem = elem
      this.container = container
      this.comp = elem.comp
      this.v = initFrame
      this.pv = initFrame
      this._isFirstFrame = true
      this.getValue = processEffectsSequence
      this.setVValue = setVValue
      this.interpolateValue = interpolateValue
      this.effectsSequence = [getValueAtCurrentTime.bind(this)]
      this.addEffect = addEffect
    }

    function KeyframedMultidimensionalProperty(elem, data, mult, container) {
      this.propType = 'multidimensional'
      var i
      var len = data.k.length
      var s
      var e
      var to
      var ti
      for (i = 0; i < len - 1; i += 1) {
        if (data.k[i].to && data.k[i].s && data.k[i + 1] && data.k[i + 1].s) {
          s = data.k[i].s
          e = data.k[i + 1].s
          to = data.k[i].to
          ti = data.k[i].ti
          if (
            (s.length === 2 &&
              !(s[0] === e[0] && s[1] === e[1]) &&
              bez.pointOnLine2D(s[0], s[1], e[0], e[1], s[0] + to[0], s[1] + to[1]) &&
              bez.pointOnLine2D(s[0], s[1], e[0], e[1], e[0] + ti[0], e[1] + ti[1])) ||
            (s.length === 3 &&
              !(s[0] === e[0] && s[1] === e[1] && s[2] === e[2]) &&
              bez.pointOnLine3D(s[0], s[1], s[2], e[0], e[1], e[2], s[0] + to[0], s[1] + to[1], s[2] + to[2]) &&
              bez.pointOnLine3D(s[0], s[1], s[2], e[0], e[1], e[2], e[0] + ti[0], e[1] + ti[1], e[2] + ti[2]))
          ) {
            data.k[i].to = null
            data.k[i].ti = null
          }
          if (s[0] === e[0] && s[1] === e[1] && to[0] === 0 && to[1] === 0 && ti[0] === 0 && ti[1] === 0) {
            if (s.length === 2 || (s[2] === e[2] && to[2] === 0 && ti[2] === 0)) {
              data.k[i].to = null
              data.k[i].ti = null
            }
          }
        }
      }
      this.effectsSequence = [getValueAtCurrentTime.bind(this)]
      this.data = data
      this.keyframes = data.k
      this.keyframesMetadata = []
      this.offsetTime = elem.data.st
      this.k = true
      this.kf = true
      this._isFirstFrame = true
      this.mult = mult || 1
      this.elem = elem
      this.container = container
      this.comp = elem.comp
      this.getValue = processEffectsSequence
      this.setVValue = setVValue
      this.interpolateValue = interpolateValue
      this.frameId = -1
      var arrLen = data.k[0].s.length
      this.v = createTypedArray('float32', arrLen)
      this.pv = createTypedArray('float32', arrLen)
      for (i = 0; i < arrLen; i += 1) {
        this.v[i] = initFrame
        this.pv[i] = initFrame
      }
      this._caching = { lastFrame: initFrame, lastIndex: 0, value: createTypedArray('float32', arrLen) }
      this.addEffect = addEffect
    }

    function getProp(elem, data, type, mult, container) {
      var p
      if (!data.k.length) {
        p = new ValueProperty(elem, data, mult, container)
      } else if (typeof data.k[0] === 'number') {
        p = new MultiDimensionalProperty(elem, data, mult, container)
      } else {
        switch (type) {
          case 0:
            p = new KeyframedValueProperty(elem, data, mult, container)
            break
          case 1:
            p = new KeyframedMultidimensionalProperty(elem, data, mult, container)
            break
          default:
            break
        }
      }
      if (p.effectsSequence.length) {
        container.addDynamicProperty(p)
      }
      return p
    }

    var ob = {
      getProp: getProp,
    }
    return ob
  })()

  function DynamicPropertyContainer() {}
  DynamicPropertyContainer.prototype = {
    addDynamicProperty: function (prop) {
      if (this.dynamicProperties.indexOf(prop) === -1) {
        this.dynamicProperties.push(prop)
        this.container.addDynamicProperty(this)
        this._isAnimated = true
      }
    },
    iterateDynamicProperties: function () {
      this._mdf = false
      var i
      var len = this.dynamicProperties.length
      for (i = 0; i < len; i += 1) {
        this.dynamicProperties[i].getValue()
        if (this.dynamicProperties[i]._mdf) {
          this._mdf = true
        }
      }
    },
    initDynamicPropertyContainer: function (container) {
      this.container = container
      this.dynamicProperties = []
      this._mdf = false
      this._isAnimated = false
    },
  }

  const pointPool = (function () {
    function create() {
      return createTypedArray('float32', 2)
    }
    return poolFactory(8, create)
  })()

  function ShapePath() {
    this.c = false
    this._length = 0
    this._maxLength = 8
    this.v = createSizedArray(this._maxLength)
    this.o = createSizedArray(this._maxLength)
    this.i = createSizedArray(this._maxLength)
  }

  ShapePath.prototype.setPathData = function (closed, len) {
    this.c = closed
    this.setLength(len)
    var i = 0
    while (i < len) {
      this.v[i] = pointPool.newElement()
      this.o[i] = pointPool.newElement()
      this.i[i] = pointPool.newElement()
      i += 1
    }
  }

  ShapePath.prototype.setLength = function (len) {
    while (this._maxLength < len) {
      this.doubleArrayLength()
    }
    this._length = len
  }

  ShapePath.prototype.doubleArrayLength = function () {
    this.v = this.v.concat(createSizedArray(this._maxLength))
    this.i = this.i.concat(createSizedArray(this._maxLength))
    this.o = this.o.concat(createSizedArray(this._maxLength))
    this._maxLength *= 2
  }

  ShapePath.prototype.setXYAt = function (x, y, type, pos, replace) {
    var arr
    this._length = Math.max(this._length, pos + 1)
    if (this._length >= this._maxLength) {
      this.doubleArrayLength()
    }
    switch (type) {
      case 'v':
        arr = this.v
        break
      case 'i':
        arr = this.i
        break
      case 'o':
        arr = this.o
        break
      default:
        arr = []
        break
    }
    if (!arr[pos] || (arr[pos] && !replace)) {
      arr[pos] = pointPool.newElement()
    }
    arr[pos][0] = x
    arr[pos][1] = y
  }

  ShapePath.prototype.setTripleAt = function (vX, vY, oX, oY, iX, iY, pos, replace) {
    this.setXYAt(vX, vY, 'v', pos, replace)
    this.setXYAt(oX, oY, 'o', pos, replace)
    this.setXYAt(iX, iY, 'i', pos, replace)
  }

  ShapePath.prototype.reverse = function () {
    var newPath = new ShapePath()
    newPath.setPathData(this.c, this._length)
    var vertices = this.v
    var outPoints = this.o
    var inPoints = this.i
    var init = 0
    if (this.c) {
      newPath.setTripleAt(
        vertices[0][0],
        vertices[0][1],
        inPoints[0][0],
        inPoints[0][1],
        outPoints[0][0],
        outPoints[0][1],
        0,
        false,
      )
      init = 1
    }
    var cnt = this._length - 1
    var len = this._length

    var i
    for (i = init; i < len; i += 1) {
      newPath.setTripleAt(
        vertices[cnt][0],
        vertices[cnt][1],
        inPoints[cnt][0],
        inPoints[cnt][1],
        outPoints[cnt][0],
        outPoints[cnt][1],
        i,
        false,
      )
      cnt -= 1
    }
    return newPath
  }

  const shapePool = (function () {
    function create() {
      return new ShapePath()
    }

    function release(shapePath) {
      var len = shapePath._length
      var i
      for (i = 0; i < len; i += 1) {
        pointPool.release(shapePath.v[i])
        pointPool.release(shapePath.i[i])
        pointPool.release(shapePath.o[i])
        shapePath.v[i] = null
        shapePath.i[i] = null
        shapePath.o[i] = null
      }
      shapePath._length = 0
      shapePath.c = false
    }

    function clone(shape) {
      var cloned = factory.newElement()
      var i
      var len = shape._length === undefined ? shape.v.length : shape._length
      cloned.setLength(len)
      cloned.c = shape.c

      for (i = 0; i < len; i += 1) {
        cloned.setTripleAt(shape.v[i][0], shape.v[i][1], shape.o[i][0], shape.o[i][1], shape.i[i][0], shape.i[i][1], i)
      }
      return cloned
    }

    var factory = poolFactory(4, create, release)
    factory.clone = clone

    return factory
  })()

  function ShapeCollection() {
    this._length = 0
    this._maxLength = 4
    this.shapes = createSizedArray(this._maxLength)
  }

  ShapeCollection.prototype.addShape = function (shapeData) {
    if (this._length === this._maxLength) {
      this.shapes = this.shapes.concat(createSizedArray(this._maxLength))
      this._maxLength *= 2
    }
    this.shapes[this._length] = shapeData
    this._length += 1
  }

  ShapeCollection.prototype.releaseShapes = function () {
    var i
    for (i = 0; i < this._length; i += 1) {
      shapePool.release(this.shapes[i])
    }
    this._length = 0
  }

  const shapeCollectionPool = (function () {
    var ob = {
      newShapeCollection: newShapeCollection,
      release: release,
    }

    var _length = 0
    var _maxLength = 4
    var pool = createSizedArray(_maxLength)

    function newShapeCollection() {
      var shapeCollection
      if (_length) {
        _length -= 1
        shapeCollection = pool[_length]
      } else {
        shapeCollection = new ShapeCollection()
      }
      return shapeCollection
    }

    function release(shapeCollection) {
      var i
      var len = shapeCollection._length
      for (i = 0; i < len; i += 1) {
        shapePool.release(shapeCollection.shapes[i])
      }
      shapeCollection._length = 0

      if (_length === _maxLength) {
        pool = pooling.double(pool)
        _maxLength *= 2
      }
      pool[_length] = shapeCollection
      _length += 1
    }

    return ob
  })()

  const ShapePropertyFactory = (function () {
    var initFrame = -999999

    function interpolateShape(frameNum, previousValue, caching) {
      var iterationIndex = caching.lastIndex
      var keyPropS
      var keyPropE
      var isHold
      var j
      var k
      var jLen
      var kLen
      var perc
      var vertexValue
      var kf = this.keyframes
      if (frameNum < kf[0].t - this.offsetTime) {
        keyPropS = kf[0].s[0]
        isHold = true
        iterationIndex = 0
      } else if (frameNum >= kf[kf.length - 1].t - this.offsetTime) {
        keyPropS = kf[kf.length - 1].s ? kf[kf.length - 1].s[0] : kf[kf.length - 2].e[0]
        /* if(kf[kf.length - 1].s){
                keyPropS = kf[kf.length - 1].s[0];
            }else{
                keyPropS = kf[kf.length - 2].e[0];
            } */
        isHold = true
      } else {
        var i = iterationIndex
        var len = kf.length - 1
        var flag = true
        var keyData
        var nextKeyData
        var keyframeMetadata
        while (flag) {
          keyData = kf[i]
          nextKeyData = kf[i + 1]
          if (nextKeyData.t - this.offsetTime > frameNum) {
            break
          }
          if (i < len - 1) {
            i += 1
          } else {
            flag = false
          }
        }
        keyframeMetadata = this.keyframesMetadata[i] || {}
        isHold = keyData.h === 1
        iterationIndex = i
        if (!isHold) {
          if (frameNum >= nextKeyData.t - this.offsetTime) {
            perc = 1
          } else if (frameNum < keyData.t - this.offsetTime) {
            perc = 0
          } else {
            var fnc
            if (keyframeMetadata.__fnct) {
              fnc = keyframeMetadata.__fnct
            } else {
              fnc = BezierFactory.getBezierEasing(keyData.o.x, keyData.o.y, keyData.i.x, keyData.i.y).get
              keyframeMetadata.__fnct = fnc
            }
            perc = fnc(
              (frameNum - (keyData.t - this.offsetTime)) /
                (nextKeyData.t - this.offsetTime - (keyData.t - this.offsetTime)),
            )
          }
          keyPropE = nextKeyData.s ? nextKeyData.s[0] : keyData.e[0]
        }
        keyPropS = keyData.s[0]
      }
      jLen = previousValue._length
      kLen = keyPropS.i[0].length
      caching.lastIndex = iterationIndex

      for (j = 0; j < jLen; j += 1) {
        for (k = 0; k < kLen; k += 1) {
          vertexValue = isHold ? keyPropS.i[j][k] : keyPropS.i[j][k] + (keyPropE.i[j][k] - keyPropS.i[j][k]) * perc
          previousValue.i[j][k] = vertexValue
          vertexValue = isHold ? keyPropS.o[j][k] : keyPropS.o[j][k] + (keyPropE.o[j][k] - keyPropS.o[j][k]) * perc
          previousValue.o[j][k] = vertexValue
          vertexValue = isHold ? keyPropS.v[j][k] : keyPropS.v[j][k] + (keyPropE.v[j][k] - keyPropS.v[j][k]) * perc
          previousValue.v[j][k] = vertexValue
        }
      }
    }

    function interpolateShapeCurrentTime() {
      var frameNum = this.comp.renderedFrame - this.offsetTime
      var initTime = this.keyframes[0].t - this.offsetTime
      var endTime = this.keyframes[this.keyframes.length - 1].t - this.offsetTime
      var lastFrame = this._caching.lastFrame
      if (
        !(
          lastFrame !== initFrame &&
          ((lastFrame < initTime && frameNum < initTime) || (lastFrame > endTime && frameNum > endTime))
        )
      ) {
        /// /
        this._caching.lastIndex = lastFrame < frameNum ? this._caching.lastIndex : 0
        this.interpolateShape(frameNum, this.pv, this._caching)
        /// /
      }
      this._caching.lastFrame = frameNum
      return this.pv
    }

    function resetShape() {
      this.paths = this.localShapeCollection
    }

    function shapesEqual(shape1, shape2) {
      if (shape1._length !== shape2._length || shape1.c !== shape2.c) {
        return false
      }
      var i
      var len = shape1._length
      for (i = 0; i < len; i += 1) {
        if (
          shape1.v[i][0] !== shape2.v[i][0] ||
          shape1.v[i][1] !== shape2.v[i][1] ||
          shape1.o[i][0] !== shape2.o[i][0] ||
          shape1.o[i][1] !== shape2.o[i][1] ||
          shape1.i[i][0] !== shape2.i[i][0] ||
          shape1.i[i][1] !== shape2.i[i][1]
        ) {
          return false
        }
      }
      return true
    }

    function setVValue(newPath) {
      if (!shapesEqual(this.v, newPath)) {
        this.v = shapePool.clone(newPath)
        this.localShapeCollection.releaseShapes()
        this.localShapeCollection.addShape(this.v)
        this._mdf = true
        this.paths = this.localShapeCollection
      }
    }

    function processEffectsSequence() {
      if (this.elem.globalData.frameId === this.frameId) {
        return
      }
      if (!this.effectsSequence.length) {
        this._mdf = false
        return
      }
      if (this.lock) {
        this.setVValue(this.pv)
        return
      }
      this.lock = true
      this._mdf = false
      var finalValue
      if (this.kf) {
        finalValue = this.pv
      } else if (this.data.ks) {
        finalValue = this.data.ks.k
      } else {
        finalValue = this.data.pt.k
      }
      var i
      var len = this.effectsSequence.length
      for (i = 0; i < len; i += 1) {
        finalValue = this.effectsSequence[i](finalValue)
      }
      this.setVValue(finalValue)
      this.lock = false
      this.frameId = this.elem.globalData.frameId
    }

    function ShapeProperty(elem, data, type) {
      this.propType = 'shape'
      this.comp = elem.comp
      this.container = elem
      this.elem = elem
      this.data = data
      this.k = false
      this.kf = false
      this._mdf = false
      var pathData = type === 3 ? data.pt.k : data.ks.k
      this.v = shapePool.clone(pathData)
      this.pv = shapePool.clone(this.v)
      this.localShapeCollection = shapeCollectionPool.newShapeCollection()
      this.paths = this.localShapeCollection
      this.paths.addShape(this.v)
      this.reset = resetShape
      this.effectsSequence = []
    }

    function addEffect(effectFunction) {
      this.effectsSequence.push(effectFunction)
      this.container.addDynamicProperty(this)
    }

    ShapeProperty.prototype.interpolateShape = interpolateShape
    ShapeProperty.prototype.getValue = processEffectsSequence
    ShapeProperty.prototype.setVValue = setVValue
    ShapeProperty.prototype.addEffect = addEffect

    function KeyframedShapeProperty(elem, data, type) {
      this.propType = 'shape'
      this.comp = elem.comp
      this.elem = elem
      this.container = elem
      this.offsetTime = elem.data.st
      this.keyframes = type === 3 ? data.pt.k : data.ks.k
      this.keyframesMetadata = []
      this.k = true
      this.kf = true
      var len = this.keyframes[0].s[0].i.length
      this.v = shapePool.newElement()
      this.v.setPathData(this.keyframes[0].s[0].c, len)
      this.pv = shapePool.clone(this.v)
      this.localShapeCollection = shapeCollectionPool.newShapeCollection()
      this.paths = this.localShapeCollection
      this.paths.addShape(this.v)
      this.lastFrame = initFrame
      this.reset = resetShape
      this._caching = { lastFrame: initFrame, lastIndex: 0 }
      this.effectsSequence = [interpolateShapeCurrentTime.bind(this)]
    }
    KeyframedShapeProperty.prototype.getValue = processEffectsSequence
    KeyframedShapeProperty.prototype.interpolateShape = interpolateShape
    KeyframedShapeProperty.prototype.setVValue = setVValue
    KeyframedShapeProperty.prototype.addEffect = addEffect

    var EllShapeProperty = (function () {
      var cPoint = roundCorner

      function EllShapePropertyFactory(elem, data) {
        this.v = shapePool.newElement()
        this.v.setPathData(true, 4)
        this.localShapeCollection = shapeCollectionPool.newShapeCollection()
        this.paths = this.localShapeCollection
        this.localShapeCollection.addShape(this.v)
        this.d = data.d
        this.elem = elem
        this.comp = elem.comp
        this.frameId = -1
        this.initDynamicPropertyContainer(elem)
        this.p = PropertyFactory.getProp(elem, data.p, 1, 0, this)
        this.s = PropertyFactory.getProp(elem, data.s, 1, 0, this)
        if (this.dynamicProperties.length) {
          this.k = true
        } else {
          this.k = false
          this.convertEllToPath()
        }
      }

      EllShapePropertyFactory.prototype = {
        reset: resetShape,
        getValue: function () {
          if (this.elem.globalData.frameId === this.frameId) {
            return
          }
          this.frameId = this.elem.globalData.frameId
          this.iterateDynamicProperties()

          if (this._mdf) {
            this.convertEllToPath()
          }
        },
        convertEllToPath: function () {
          var p0 = this.p.v[0]
          var p1 = this.p.v[1]
          var s0 = this.s.v[0] / 2
          var s1 = this.s.v[1] / 2
          var _cw = this.d !== 3
          var _v = this.v
          _v.v[0][0] = p0
          _v.v[0][1] = p1 - s1
          _v.v[1][0] = _cw ? p0 + s0 : p0 - s0
          _v.v[1][1] = p1
          _v.v[2][0] = p0
          _v.v[2][1] = p1 + s1
          _v.v[3][0] = _cw ? p0 - s0 : p0 + s0
          _v.v[3][1] = p1
          _v.i[0][0] = _cw ? p0 - s0 * cPoint : p0 + s0 * cPoint
          _v.i[0][1] = p1 - s1
          _v.i[1][0] = _cw ? p0 + s0 : p0 - s0
          _v.i[1][1] = p1 - s1 * cPoint
          _v.i[2][0] = _cw ? p0 + s0 * cPoint : p0 - s0 * cPoint
          _v.i[2][1] = p1 + s1
          _v.i[3][0] = _cw ? p0 - s0 : p0 + s0
          _v.i[3][1] = p1 + s1 * cPoint
          _v.o[0][0] = _cw ? p0 + s0 * cPoint : p0 - s0 * cPoint
          _v.o[0][1] = p1 - s1
          _v.o[1][0] = _cw ? p0 + s0 : p0 - s0
          _v.o[1][1] = p1 + s1 * cPoint
          _v.o[2][0] = _cw ? p0 - s0 * cPoint : p0 + s0 * cPoint
          _v.o[2][1] = p1 + s1
          _v.o[3][0] = _cw ? p0 - s0 : p0 + s0
          _v.o[3][1] = p1 - s1 * cPoint
        },
      }

      extendPrototype([DynamicPropertyContainer], EllShapePropertyFactory)

      return EllShapePropertyFactory
    })()

    var StarShapeProperty = (function () {
      function StarShapePropertyFactory(elem, data) {
        this.v = shapePool.newElement()
        this.v.setPathData(true, 0)
        this.elem = elem
        this.comp = elem.comp
        this.data = data
        this.frameId = -1
        this.d = data.d
        this.initDynamicPropertyContainer(elem)
        if (data.sy === 1) {
          this.ir = PropertyFactory.getProp(elem, data.ir, 0, 0, this)
          this.is = PropertyFactory.getProp(elem, data.is, 0, 0.01, this)
          this.convertToPath = this.convertStarToPath
        } else {
          this.convertToPath = this.convertPolygonToPath
        }
        this.pt = PropertyFactory.getProp(elem, data.pt, 0, 0, this)
        this.p = PropertyFactory.getProp(elem, data.p, 1, 0, this)
        this.r = PropertyFactory.getProp(elem, data.r, 0, degToRads, this)
        this.or = PropertyFactory.getProp(elem, data.or, 0, 0, this)
        this.os = PropertyFactory.getProp(elem, data.os, 0, 0.01, this)
        this.localShapeCollection = shapeCollectionPool.newShapeCollection()
        this.localShapeCollection.addShape(this.v)
        this.paths = this.localShapeCollection
        if (this.dynamicProperties.length) {
          this.k = true
        } else {
          this.k = false
          this.convertToPath()
        }
      }

      StarShapePropertyFactory.prototype = {
        reset: resetShape,
        getValue: function () {
          if (this.elem.globalData.frameId === this.frameId) {
            return
          }
          this.frameId = this.elem.globalData.frameId
          this.iterateDynamicProperties()
          if (this._mdf) {
            this.convertToPath()
          }
        },
        convertStarToPath: function () {
          var numPts = Math.floor(this.pt.v) * 2
          var angle = (Math.PI * 2) / numPts
          /* this.v.v.length = numPts;
                this.v.i.length = numPts;
                this.v.o.length = numPts; */
          var longFlag = true
          var longRad = this.or.v
          var shortRad = this.ir.v
          var longRound = this.os.v
          var shortRound = this.is.v
          var longPerimSegment = (2 * Math.PI * longRad) / (numPts * 2)
          var shortPerimSegment = (2 * Math.PI * shortRad) / (numPts * 2)
          var i
          var rad
          var roundness
          var perimSegment
          var currentAng = -Math.PI / 2
          currentAng += this.r.v
          var dir = this.data.d === 3 ? -1 : 1
          this.v._length = 0
          for (i = 0; i < numPts; i += 1) {
            rad = longFlag ? longRad : shortRad
            roundness = longFlag ? longRound : shortRound
            perimSegment = longFlag ? longPerimSegment : shortPerimSegment
            var x = rad * Math.cos(currentAng)
            var y = rad * Math.sin(currentAng)
            var ox = x === 0 && y === 0 ? 0 : y / Math.sqrt(x * x + y * y)
            var oy = x === 0 && y === 0 ? 0 : -x / Math.sqrt(x * x + y * y)
            x += +this.p.v[0]
            y += +this.p.v[1]
            this.v.setTripleAt(
              x,
              y,
              x - ox * perimSegment * roundness * dir,
              y - oy * perimSegment * roundness * dir,
              x + ox * perimSegment * roundness * dir,
              y + oy * perimSegment * roundness * dir,
              i,
              true,
            )

            /* this.v.v[i] = [x,y];
                    this.v.i[i] = [x+ox*perimSegment*roundness*dir,y+oy*perimSegment*roundness*dir];
                    this.v.o[i] = [x-ox*perimSegment*roundness*dir,y-oy*perimSegment*roundness*dir];
                    this.v._length = numPts; */
            longFlag = !longFlag
            currentAng += angle * dir
          }
        },
        convertPolygonToPath: function () {
          var numPts = Math.floor(this.pt.v)
          var angle = (Math.PI * 2) / numPts
          var rad = this.or.v
          var roundness = this.os.v
          var perimSegment = (2 * Math.PI * rad) / (numPts * 4)
          var i
          var currentAng = -Math.PI * 0.5
          var dir = this.data.d === 3 ? -1 : 1
          currentAng += this.r.v
          this.v._length = 0
          for (i = 0; i < numPts; i += 1) {
            var x = rad * Math.cos(currentAng)
            var y = rad * Math.sin(currentAng)
            var ox = x === 0 && y === 0 ? 0 : y / Math.sqrt(x * x + y * y)
            var oy = x === 0 && y === 0 ? 0 : -x / Math.sqrt(x * x + y * y)
            x += +this.p.v[0]
            y += +this.p.v[1]
            this.v.setTripleAt(
              x,
              y,
              x - ox * perimSegment * roundness * dir,
              y - oy * perimSegment * roundness * dir,
              x + ox * perimSegment * roundness * dir,
              y + oy * perimSegment * roundness * dir,
              i,
              true,
            )
            currentAng += angle * dir
          }
          this.paths.length = 0
          this.paths[0] = this.v
        },
      }
      extendPrototype([DynamicPropertyContainer], StarShapePropertyFactory)

      return StarShapePropertyFactory
    })()

    var RectShapeProperty = (function () {
      function RectShapePropertyFactory(elem, data) {
        this.v = shapePool.newElement()
        this.v.c = true
        this.localShapeCollection = shapeCollectionPool.newShapeCollection()
        this.localShapeCollection.addShape(this.v)
        this.paths = this.localShapeCollection
        this.elem = elem
        this.comp = elem.comp
        this.frameId = -1
        this.d = data.d
        this.initDynamicPropertyContainer(elem)
        this.p = PropertyFactory.getProp(elem, data.p, 1, 0, this)
        this.s = PropertyFactory.getProp(elem, data.s, 1, 0, this)
        this.r = PropertyFactory.getProp(elem, data.r, 0, 0, this)
        if (this.dynamicProperties.length) {
          this.k = true
        } else {
          this.k = false
          this.convertRectToPath()
        }
      }

      RectShapePropertyFactory.prototype = {
        convertRectToPath: function () {
          var p0 = this.p.v[0]
          var p1 = this.p.v[1]
          var v0 = this.s.v[0] / 2
          var v1 = this.s.v[1] / 2
          var round = bmMin(v0, v1, this.r.v)
          var cPoint = round * (1 - roundCorner)
          this.v._length = 0

          if (this.d === 2 || this.d === 1) {
            this.v.setTripleAt(p0 + v0, p1 - v1 + round, p0 + v0, p1 - v1 + round, p0 + v0, p1 - v1 + cPoint, 0, true)
            this.v.setTripleAt(p0 + v0, p1 + v1 - round, p0 + v0, p1 + v1 - cPoint, p0 + v0, p1 + v1 - round, 1, true)
            if (round !== 0) {
              this.v.setTripleAt(p0 + v0 - round, p1 + v1, p0 + v0 - round, p1 + v1, p0 + v0 - cPoint, p1 + v1, 2, true)
              this.v.setTripleAt(p0 - v0 + round, p1 + v1, p0 - v0 + cPoint, p1 + v1, p0 - v0 + round, p1 + v1, 3, true)
              this.v.setTripleAt(p0 - v0, p1 + v1 - round, p0 - v0, p1 + v1 - round, p0 - v0, p1 + v1 - cPoint, 4, true)
              this.v.setTripleAt(p0 - v0, p1 - v1 + round, p0 - v0, p1 - v1 + cPoint, p0 - v0, p1 - v1 + round, 5, true)
              this.v.setTripleAt(p0 - v0 + round, p1 - v1, p0 - v0 + round, p1 - v1, p0 - v0 + cPoint, p1 - v1, 6, true)
              this.v.setTripleAt(p0 + v0 - round, p1 - v1, p0 + v0 - cPoint, p1 - v1, p0 + v0 - round, p1 - v1, 7, true)
            } else {
              this.v.setTripleAt(p0 - v0, p1 + v1, p0 - v0 + cPoint, p1 + v1, p0 - v0, p1 + v1, 2)
              this.v.setTripleAt(p0 - v0, p1 - v1, p0 - v0, p1 - v1 + cPoint, p0 - v0, p1 - v1, 3)
            }
          } else {
            this.v.setTripleAt(p0 + v0, p1 - v1 + round, p0 + v0, p1 - v1 + cPoint, p0 + v0, p1 - v1 + round, 0, true)
            if (round !== 0) {
              this.v.setTripleAt(p0 + v0 - round, p1 - v1, p0 + v0 - round, p1 - v1, p0 + v0 - cPoint, p1 - v1, 1, true)
              this.v.setTripleAt(p0 - v0 + round, p1 - v1, p0 - v0 + cPoint, p1 - v1, p0 - v0 + round, p1 - v1, 2, true)
              this.v.setTripleAt(p0 - v0, p1 - v1 + round, p0 - v0, p1 - v1 + round, p0 - v0, p1 - v1 + cPoint, 3, true)
              this.v.setTripleAt(p0 - v0, p1 + v1 - round, p0 - v0, p1 + v1 - cPoint, p0 - v0, p1 + v1 - round, 4, true)
              this.v.setTripleAt(p0 - v0 + round, p1 + v1, p0 - v0 + round, p1 + v1, p0 - v0 + cPoint, p1 + v1, 5, true)
              this.v.setTripleAt(p0 + v0 - round, p1 + v1, p0 + v0 - cPoint, p1 + v1, p0 + v0 - round, p1 + v1, 6, true)
              this.v.setTripleAt(p0 + v0, p1 + v1 - round, p0 + v0, p1 + v1 - round, p0 + v0, p1 + v1 - cPoint, 7, true)
            } else {
              this.v.setTripleAt(p0 - v0, p1 - v1, p0 - v0 + cPoint, p1 - v1, p0 - v0, p1 - v1, 1, true)
              this.v.setTripleAt(p0 - v0, p1 + v1, p0 - v0, p1 + v1 - cPoint, p0 - v0, p1 + v1, 2, true)
              this.v.setTripleAt(p0 + v0, p1 + v1, p0 + v0 - cPoint, p1 + v1, p0 + v0, p1 + v1, 3, true)
            }
          }
        },
        getValue: function () {
          if (this.elem.globalData.frameId === this.frameId) {
            return
          }
          this.frameId = this.elem.globalData.frameId
          this.iterateDynamicProperties()
          if (this._mdf) {
            this.convertRectToPath()
          }
        },
        reset: resetShape,
      }
      extendPrototype([DynamicPropertyContainer], RectShapePropertyFactory)

      return RectShapePropertyFactory
    })()

    function getShapeProp(elem, data, type) {
      var prop
      if (type === 3 || type === 4) {
        var dataProp = type === 3 ? data.pt : data.ks
        var keys = dataProp.k
        if (keys.length) {
          prop = new KeyframedShapeProperty(elem, data, type)
        } else {
          prop = new ShapeProperty(elem, data, type)
        }
      } else if (type === 5) {
        prop = new RectShapeProperty(elem, data)
      } else if (type === 6) {
        prop = new EllShapeProperty(elem, data)
      } else if (type === 7) {
        prop = new StarShapeProperty(elem, data)
      }
      if (prop.k) {
        elem.addDynamicProperty(prop)
      }
      return prop
    }

    function getConstructorFunction() {
      return ShapeProperty
    }

    function getKeyframedConstructorFunction() {
      return KeyframedShapeProperty
    }

    var ob = {}
    ob.getShapeProp = getShapeProp
    ob.getConstructorFunction = getConstructorFunction
    ob.getKeyframedConstructorFunction = getKeyframedConstructorFunction
    return ob
  })()

  /*!
 Transformation Matrix v2.0
 (c) Epistemex 2014-2015
 www.epistemex.com
 By Ken Fyrstenberg
 Contributions by leeoniya.
 License: MIT, header required.
 */

  /**
   * 2D transformation matrix object initialized with identity matrix.
   *
   * The matrix can synchronize a canvas context by supplying the context
   * as an argument, or later apply current absolute transform to an
   * existing context.
   *
   * All values are handled as floating point values.
   *
   * @param {CanvasRenderingContext2D} [context] - Optional context to sync with Matrix
   * @prop {number} a - scale x
   * @prop {number} b - shear y
   * @prop {number} c - shear x
   * @prop {number} d - scale y
   * @prop {number} e - translate x
   * @prop {number} f - translate y
   * @prop {CanvasRenderingContext2D|null} [context=null] - set or get current canvas context
   * @constructor
   */

  const Matrix = (function () {
    var _cos = Math.cos
    var _sin = Math.sin
    var _tan = Math.tan
    var _rnd = Math.round

    function reset() {
      this.props[0] = 1
      this.props[1] = 0
      this.props[2] = 0
      this.props[3] = 0
      this.props[4] = 0
      this.props[5] = 1
      this.props[6] = 0
      this.props[7] = 0
      this.props[8] = 0
      this.props[9] = 0
      this.props[10] = 1
      this.props[11] = 0
      this.props[12] = 0
      this.props[13] = 0
      this.props[14] = 0
      this.props[15] = 1
      return this
    }

    function rotate(angle) {
      if (angle === 0) {
        return this
      }
      var mCos = _cos(angle)
      var mSin = _sin(angle)
      return this._t(mCos, -mSin, 0, 0, mSin, mCos, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)
    }

    function rotateX(angle) {
      if (angle === 0) {
        return this
      }
      var mCos = _cos(angle)
      var mSin = _sin(angle)
      return this._t(1, 0, 0, 0, 0, mCos, -mSin, 0, 0, mSin, mCos, 0, 0, 0, 0, 1)
    }

    function rotateY(angle) {
      if (angle === 0) {
        return this
      }
      var mCos = _cos(angle)
      var mSin = _sin(angle)
      return this._t(mCos, 0, mSin, 0, 0, 1, 0, 0, -mSin, 0, mCos, 0, 0, 0, 0, 1)
    }

    function rotateZ(angle) {
      if (angle === 0) {
        return this
      }
      var mCos = _cos(angle)
      var mSin = _sin(angle)
      return this._t(mCos, -mSin, 0, 0, mSin, mCos, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)
    }

    function shear(sx, sy) {
      return this._t(1, sy, sx, 1, 0, 0)
    }

    function skew(ax, ay) {
      return this.shear(_tan(ax), _tan(ay))
    }

    function skewFromAxis(ax, angle) {
      var mCos = _cos(angle)
      var mSin = _sin(angle)
      return this._t(mCos, mSin, 0, 0, -mSin, mCos, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)
        ._t(1, 0, 0, 0, _tan(ax), 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)
        ._t(mCos, -mSin, 0, 0, mSin, mCos, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)
      // return this._t(mCos, mSin, -mSin, mCos, 0, 0)._t(1, 0, _tan(ax), 1, 0, 0)._t(mCos, -mSin, mSin, mCos, 0, 0);
    }

    function scale(sx, sy, sz) {
      if (!sz && sz !== 0) {
        sz = 1
      }
      if (sx === 1 && sy === 1 && sz === 1) {
        return this
      }
      return this._t(sx, 0, 0, 0, 0, sy, 0, 0, 0, 0, sz, 0, 0, 0, 0, 1)
    }

    function setTransform(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) {
      this.props[0] = a
      this.props[1] = b
      this.props[2] = c
      this.props[3] = d
      this.props[4] = e
      this.props[5] = f
      this.props[6] = g
      this.props[7] = h
      this.props[8] = i
      this.props[9] = j
      this.props[10] = k
      this.props[11] = l
      this.props[12] = m
      this.props[13] = n
      this.props[14] = o
      this.props[15] = p
      return this
    }

    function translate(tx, ty, tz) {
      tz = tz || 0
      if (tx !== 0 || ty !== 0 || tz !== 0) {
        return this._t(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, tx, ty, tz, 1)
      }
      return this
    }

    function transform(a2, b2, c2, d2, e2, f2, g2, h2, i2, j2, k2, l2, m2, n2, o2, p2) {
      var _p = this.props

      if (
        a2 === 1 &&
        b2 === 0 &&
        c2 === 0 &&
        d2 === 0 &&
        e2 === 0 &&
        f2 === 1 &&
        g2 === 0 &&
        h2 === 0 &&
        i2 === 0 &&
        j2 === 0 &&
        k2 === 1 &&
        l2 === 0
      ) {
        // NOTE: commenting this condition because TurboFan deoptimizes code when present
        // if(m2 !== 0 || n2 !== 0 || o2 !== 0){
        _p[12] = _p[12] * a2 + _p[15] * m2
        _p[13] = _p[13] * f2 + _p[15] * n2
        _p[14] = _p[14] * k2 + _p[15] * o2
        _p[15] *= p2
        // }
        this._identityCalculated = false
        return this
      }

      var a1 = _p[0]
      var b1 = _p[1]
      var c1 = _p[2]
      var d1 = _p[3]
      var e1 = _p[4]
      var f1 = _p[5]
      var g1 = _p[6]
      var h1 = _p[7]
      var i1 = _p[8]
      var j1 = _p[9]
      var k1 = _p[10]
      var l1 = _p[11]
      var m1 = _p[12]
      var n1 = _p[13]
      var o1 = _p[14]
      var p1 = _p[15]

      /* matrix order (canvas compatible):
       * ace
       * bdf
       * 001
       */
      _p[0] = a1 * a2 + b1 * e2 + c1 * i2 + d1 * m2
      _p[1] = a1 * b2 + b1 * f2 + c1 * j2 + d1 * n2
      _p[2] = a1 * c2 + b1 * g2 + c1 * k2 + d1 * o2
      _p[3] = a1 * d2 + b1 * h2 + c1 * l2 + d1 * p2

      _p[4] = e1 * a2 + f1 * e2 + g1 * i2 + h1 * m2
      _p[5] = e1 * b2 + f1 * f2 + g1 * j2 + h1 * n2
      _p[6] = e1 * c2 + f1 * g2 + g1 * k2 + h1 * o2
      _p[7] = e1 * d2 + f1 * h2 + g1 * l2 + h1 * p2

      _p[8] = i1 * a2 + j1 * e2 + k1 * i2 + l1 * m2
      _p[9] = i1 * b2 + j1 * f2 + k1 * j2 + l1 * n2
      _p[10] = i1 * c2 + j1 * g2 + k1 * k2 + l1 * o2
      _p[11] = i1 * d2 + j1 * h2 + k1 * l2 + l1 * p2

      _p[12] = m1 * a2 + n1 * e2 + o1 * i2 + p1 * m2
      _p[13] = m1 * b2 + n1 * f2 + o1 * j2 + p1 * n2
      _p[14] = m1 * c2 + n1 * g2 + o1 * k2 + p1 * o2
      _p[15] = m1 * d2 + n1 * h2 + o1 * l2 + p1 * p2

      this._identityCalculated = false
      return this
    }

    function isIdentity() {
      if (!this._identityCalculated) {
        this._identity = !(
          this.props[0] !== 1 ||
          this.props[1] !== 0 ||
          this.props[2] !== 0 ||
          this.props[3] !== 0 ||
          this.props[4] !== 0 ||
          this.props[5] !== 1 ||
          this.props[6] !== 0 ||
          this.props[7] !== 0 ||
          this.props[8] !== 0 ||
          this.props[9] !== 0 ||
          this.props[10] !== 1 ||
          this.props[11] !== 0 ||
          this.props[12] !== 0 ||
          this.props[13] !== 0 ||
          this.props[14] !== 0 ||
          this.props[15] !== 1
        )
        this._identityCalculated = true
      }
      return this._identity
    }

    function equals(matr) {
      var i = 0
      while (i < 16) {
        if (matr.props[i] !== this.props[i]) {
          return false
        }
        i += 1
      }
      return true
    }

    function clone(matr) {
      var i
      for (i = 0; i < 16; i += 1) {
        matr.props[i] = this.props[i]
      }
      return matr
    }

    function cloneFromProps(props) {
      var i
      for (i = 0; i < 16; i += 1) {
        this.props[i] = props[i]
      }
    }

    function applyToPoint(x, y, z) {
      return {
        x: x * this.props[0] + y * this.props[4] + z * this.props[8] + this.props[12],
        y: x * this.props[1] + y * this.props[5] + z * this.props[9] + this.props[13],
        z: x * this.props[2] + y * this.props[6] + z * this.props[10] + this.props[14],
      }
      /* return {
         x: x * me.a + y * me.c + me.e,
         y: x * me.b + y * me.d + me.f
         }; */
    }
    function applyToX(x, y, z) {
      return x * this.props[0] + y * this.props[4] + z * this.props[8] + this.props[12]
    }
    function applyToY(x, y, z) {
      return x * this.props[1] + y * this.props[5] + z * this.props[9] + this.props[13]
    }
    function applyToZ(x, y, z) {
      return x * this.props[2] + y * this.props[6] + z * this.props[10] + this.props[14]
    }

    function getInverseMatrix() {
      var determinant = this.props[0] * this.props[5] - this.props[1] * this.props[4]
      var a = this.props[5] / determinant
      var b = -this.props[1] / determinant
      var c = -this.props[4] / determinant
      var d = this.props[0] / determinant
      var e = (this.props[4] * this.props[13] - this.props[5] * this.props[12]) / determinant
      var f = -(this.props[0] * this.props[13] - this.props[1] * this.props[12]) / determinant
      var inverseMatrix = new Matrix()
      inverseMatrix.props[0] = a
      inverseMatrix.props[1] = b
      inverseMatrix.props[4] = c
      inverseMatrix.props[5] = d
      inverseMatrix.props[12] = e
      inverseMatrix.props[13] = f
      return inverseMatrix
    }

    function inversePoint(pt) {
      var inverseMatrix = this.getInverseMatrix()
      return inverseMatrix.applyToPointArray(pt[0], pt[1], pt[2] || 0)
    }

    function inversePoints(pts) {
      var i
      var len = pts.length
      var retPts = []
      for (i = 0; i < len; i += 1) {
        retPts[i] = inversePoint(pts[i])
      }
      return retPts
    }

    function applyToTriplePoints(pt1, pt2, pt3) {
      var arr = createTypedArray('float32', 6)
      if (this.isIdentity()) {
        arr[0] = pt1[0]
        arr[1] = pt1[1]
        arr[2] = pt2[0]
        arr[3] = pt2[1]
        arr[4] = pt3[0]
        arr[5] = pt3[1]
      } else {
        var p0 = this.props[0]
        var p1 = this.props[1]
        var p4 = this.props[4]
        var p5 = this.props[5]
        var p12 = this.props[12]
        var p13 = this.props[13]
        arr[0] = pt1[0] * p0 + pt1[1] * p4 + p12
        arr[1] = pt1[0] * p1 + pt1[1] * p5 + p13
        arr[2] = pt2[0] * p0 + pt2[1] * p4 + p12
        arr[3] = pt2[0] * p1 + pt2[1] * p5 + p13
        arr[4] = pt3[0] * p0 + pt3[1] * p4 + p12
        arr[5] = pt3[0] * p1 + pt3[1] * p5 + p13
      }
      return arr
    }

    function applyToPointArray(x, y, z) {
      var arr
      if (this.isIdentity()) {
        arr = [x, y, z]
      } else {
        arr = [
          x * this.props[0] + y * this.props[4] + z * this.props[8] + this.props[12],
          x * this.props[1] + y * this.props[5] + z * this.props[9] + this.props[13],
          x * this.props[2] + y * this.props[6] + z * this.props[10] + this.props[14],
        ]
      }
      return arr
    }

    function applyToPointStringified(x, y) {
      if (this.isIdentity()) {
        return x + ',' + y
      }
      var _p = this.props
      return (
        Math.round((x * _p[0] + y * _p[4] + _p[12]) * 100) / 100 +
        ',' +
        Math.round((x * _p[1] + y * _p[5] + _p[13]) * 100) / 100
      )
    }

    function toCSS() {
      // Doesn't make much sense to add this optimization. If it is an identity matrix, it's very likely this will get called only once since it won't be keyframed.
      /* if(this.isIdentity()) {
            return '';
        } */
      var i = 0
      var props = this.props
      var cssValue = 'matrix3d('
      var v = 10000
      while (i < 16) {
        cssValue += _rnd(props[i] * v) / v
        cssValue += i === 15 ? ')' : ','
        i += 1
      }
      return cssValue
    }

    function roundMatrixProperty(val) {
      var v = 10000
      if ((val < 0.000001 && val > 0) || (val > -0.000001 && val < 0)) {
        return _rnd(val * v) / v
      }
      return val
    }

    function to2dCSS() {
      // Doesn't make much sense to add this optimization. If it is an identity matrix, it's very likely this will get called only once since it won't be keyframed.
      /* if(this.isIdentity()) {
            return '';
        } */
      var props = this.props
      var _a = roundMatrixProperty(props[0])
      var _b = roundMatrixProperty(props[1])
      var _c = roundMatrixProperty(props[4])
      var _d = roundMatrixProperty(props[5])
      var _e = roundMatrixProperty(props[12])
      var _f = roundMatrixProperty(props[13])
      return 'matrix(' + _a + ',' + _b + ',' + _c + ',' + _d + ',' + _e + ',' + _f + ')'
    }

    return function () {
      this.reset = reset
      this.rotate = rotate
      this.rotateX = rotateX
      this.rotateY = rotateY
      this.rotateZ = rotateZ
      this.skew = skew
      this.skewFromAxis = skewFromAxis
      this.shear = shear
      this.scale = scale
      this.setTransform = setTransform
      this.translate = translate
      this.transform = transform
      this.applyToPoint = applyToPoint
      this.applyToX = applyToX
      this.applyToY = applyToY
      this.applyToZ = applyToZ
      this.applyToPointArray = applyToPointArray
      this.applyToTriplePoints = applyToTriplePoints
      this.applyToPointStringified = applyToPointStringified
      this.toCSS = toCSS
      this.to2dCSS = to2dCSS
      this.clone = clone
      this.cloneFromProps = cloneFromProps
      this.equals = equals
      this.inversePoints = inversePoints
      this.inversePoint = inversePoint
      this.getInverseMatrix = getInverseMatrix
      this._t = this.transform
      this.isIdentity = isIdentity
      this._identity = true
      this._identityCalculated = false

      this.props = createTypedArray('float32', 16)
      this.reset()
    }
  })()

  const lottie = {}
  var standalone = '__[STANDALONE]__'
  var animationData = '__[ANIMATIONDATA]__'
  var renderer = ''

  function setLocation(href) {
    setLocationHref(href)
  }

  function searchAnimations() {
    if (standalone === true) {
      animationManager.searchAnimations(animationData, standalone, renderer)
    } else {
      animationManager.searchAnimations()
    }
  }

  function setSubframeRendering(flag) {
    setSubframeEnabled(flag)
  }

  function setPrefix(prefix) {
    setIdPrefix(prefix)
  }

  function loadAnimation(params) {
    if (standalone === true) {
      params.animationData = JSON.parse(animationData)
    }
    return animationManager.loadAnimation(params)
  }

  function setQuality(value) {
    if (typeof value === 'string') {
      switch (value) {
        case 'high':
          setDefaultCurveSegments(200)
          break
        default:
        case 'medium':
          setDefaultCurveSegments(50)
          break
        case 'low':
          setDefaultCurveSegments(10)
          break
      }
    } else if (!isNaN(value) && value > 1) {
      setDefaultCurveSegments(value)
    }
    if (getDefaultCurveSegments() >= 50) {
      roundValues(false)
    } else {
      roundValues(true)
    }
  }

  function inBrowser() {
    return typeof navigator !== 'undefined'
  }

  function installPlugin(type, plugin) {
    if (type === 'expressions') {
      setExpressionsPlugin(plugin)
    }
  }

  function getFactory(name) {
    switch (name) {
      case 'propertyFactory':
        return PropertyFactory
      case 'shapePropertyFactory':
        return ShapePropertyFactory
      case 'matrix':
        return Matrix
      default:
        return null
    }
  }

  lottie.play = animationManager.play
  lottie.pause = animationManager.pause
  lottie.setLocationHref = setLocation
  lottie.togglePause = animationManager.togglePause
  lottie.setSpeed = animationManager.setSpeed
  lottie.setDirection = animationManager.setDirection
  lottie.stop = animationManager.stop
  lottie.searchAnimations = searchAnimations
  lottie.registerAnimation = animationManager.registerAnimation
  lottie.loadAnimation = loadAnimation
  lottie.setSubframeRendering = setSubframeRendering
  lottie.resize = animationManager.resize
  // lottie.start = start;
  lottie.goToAndStop = animationManager.goToAndStop
  lottie.destroy = animationManager.destroy
  lottie.setQuality = setQuality
  lottie.inBrowser = inBrowser
  lottie.installPlugin = installPlugin
  lottie.freeze = animationManager.freeze
  lottie.unfreeze = animationManager.unfreeze
  lottie.setVolume = animationManager.setVolume
  lottie.mute = animationManager.mute
  lottie.unmute = animationManager.unmute
  lottie.getRegisteredAnimations = animationManager.getRegisteredAnimations
  lottie.useWebWorker = setWebWorker
  lottie.setIDPrefix = setPrefix
  lottie.__getFactory = getFactory
  lottie.version = '[[BM_VERSION]]'

  function checkReady() {
    if (document.readyState === 'complete') {
      clearInterval(readyStateCheckInterval)
      searchAnimations()
    }
  }

  function getQueryVariable(variable) {
    var vars = queryString.split('&')
    for (var i = 0; i < vars.length; i += 1) {
      var pair = vars[i].split('=')
      if (decodeURIComponent(pair[0]) == variable) {
        // eslint-disable-line eqeqeq
        return decodeURIComponent(pair[1])
      }
    }
    return null
  }
  var queryString = ''
  if (standalone) {
    var scripts = document.getElementsByTagName('script')
    var index = scripts.length - 1
    var myScript = scripts[index] || {
      src: '',
    }
    queryString = myScript.src ? myScript.src.replace(/^[^\?]+\??/, '') : '' // eslint-disable-line no-useless-escape
    renderer = getQueryVariable('renderer')
  }
  var readyStateCheckInterval = setInterval(checkReady, 100)

  // this adds bodymovin to the window object for backwards compatibility
  try {
    if (
      !(typeof exports === 'object' && typeof module !== 'undefined') &&
      !(typeof define === 'function' && define.amd) // eslint-disable-line no-undef
    ) {
      window.bodymovin = lottie
    }
  } catch (err) {
    //
  }

  const ShapeModifiers = (function () {
    var ob = {}
    var modifiers = {}
    ob.registerModifier = registerModifier
    ob.getModifier = getModifier

    function registerModifier(nm, factory) {
      if (!modifiers[nm]) {
        modifiers[nm] = factory
      }
    }

    function getModifier(nm, elem, data) {
      return new modifiers[nm](elem, data)
    }

    return ob
  })()

  function ShapeModifier() {}
  ShapeModifier.prototype.initModifierProperties = function () {}
  ShapeModifier.prototype.addShapeToModifier = function () {}
  ShapeModifier.prototype.addShape = function (data) {
    if (!this.closed) {
      // Adding shape to dynamic properties. It covers the case where a shape has no effects applied, to reset it's _mdf state on every tick.
      data.sh.container.addDynamicProperty(data.sh)
      var shapeData = { shape: data.sh, data: data, localShapeCollection: shapeCollectionPool.newShapeCollection() }
      this.shapes.push(shapeData)
      this.addShapeToModifier(shapeData)
      if (this._isAnimated) {
        data.setAsAnimated()
      }
    }
  }
  ShapeModifier.prototype.init = function (elem, data) {
    this.shapes = []
    this.elem = elem
    this.initDynamicPropertyContainer(elem)
    this.initModifierProperties(elem, data)
    this.frameId = initialDefaultFrame
    this.closed = false
    this.k = false
    if (this.dynamicProperties.length) {
      this.k = true
    } else {
      this.getValue(true)
    }
  }
  ShapeModifier.prototype.processKeys = function () {
    if (this.elem.globalData.frameId === this.frameId) {
      return
    }
    this.frameId = this.elem.globalData.frameId
    this.iterateDynamicProperties()
  }

  extendPrototype([DynamicPropertyContainer], ShapeModifier)

  function TrimModifier() {}
  extendPrototype([ShapeModifier], TrimModifier)
  TrimModifier.prototype.initModifierProperties = function (elem, data) {
    this.s = PropertyFactory.getProp(elem, data.s, 0, 0.01, this)
    this.e = PropertyFactory.getProp(elem, data.e, 0, 0.01, this)
    this.o = PropertyFactory.getProp(elem, data.o, 0, 0, this)
    this.sValue = 0
    this.eValue = 0
    this.getValue = this.processKeys
    this.m = data.m
    this._isAnimated =
      !!this.s.effectsSequence.length || !!this.e.effectsSequence.length || !!this.o.effectsSequence.length
  }

  TrimModifier.prototype.addShapeToModifier = function (shapeData) {
    shapeData.pathsData = []
  }

  TrimModifier.prototype.calculateShapeEdges = function (s, e, shapeLength, addedLength, totalModifierLength) {
    var segments = []
    if (e <= 1) {
      segments.push({
        s: s,
        e: e,
      })
    } else if (s >= 1) {
      segments.push({
        s: s - 1,
        e: e - 1,
      })
    } else {
      segments.push({
        s: s,
        e: 1,
      })
      segments.push({
        s: 0,
        e: e - 1,
      })
    }
    var shapeSegments = []
    var i
    var len = segments.length
    var segmentOb
    for (i = 0; i < len; i += 1) {
      segmentOb = segments[i]
      if (
        !(
          segmentOb.e * totalModifierLength < addedLength ||
          segmentOb.s * totalModifierLength > addedLength + shapeLength
        )
      ) {
        var shapeS
        var shapeE
        if (segmentOb.s * totalModifierLength <= addedLength) {
          shapeS = 0
        } else {
          shapeS = (segmentOb.s * totalModifierLength - addedLength) / shapeLength
        }
        if (segmentOb.e * totalModifierLength >= addedLength + shapeLength) {
          shapeE = 1
        } else {
          shapeE = (segmentOb.e * totalModifierLength - addedLength) / shapeLength
        }
        shapeSegments.push([shapeS, shapeE])
      }
    }
    if (!shapeSegments.length) {
      shapeSegments.push([0, 0])
    }
    return shapeSegments
  }

  TrimModifier.prototype.releasePathsData = function (pathsData) {
    var i
    var len = pathsData.length
    for (i = 0; i < len; i += 1) {
      segmentsLengthPool.release(pathsData[i])
    }
    pathsData.length = 0
    return pathsData
  }

  TrimModifier.prototype.processShapes = function (_isFirstFrame) {
    var s
    var e
    if (this._mdf || _isFirstFrame) {
      var o = (this.o.v % 360) / 360
      if (o < 0) {
        o += 1
      }
      if (this.s.v > 1) {
        s = 1 + o
      } else if (this.s.v < 0) {
        s = 0 + o
      } else {
        s = this.s.v + o
      }
      if (this.e.v > 1) {
        e = 1 + o
      } else if (this.e.v < 0) {
        e = 0 + o
      } else {
        e = this.e.v + o
      }

      if (s > e) {
        var _s = s
        s = e
        e = _s
      }
      s = Math.round(s * 10000) * 0.0001
      e = Math.round(e * 10000) * 0.0001
      this.sValue = s
      this.eValue = e
    } else {
      s = this.sValue
      e = this.eValue
    }
    var shapePaths
    var i
    var len = this.shapes.length
    var j
    var jLen
    var pathsData
    var pathData
    var totalShapeLength
    var totalModifierLength = 0

    if (e === s) {
      for (i = 0; i < len; i += 1) {
        this.shapes[i].localShapeCollection.releaseShapes()
        this.shapes[i].shape._mdf = true
        this.shapes[i].shape.paths = this.shapes[i].localShapeCollection
        if (this._mdf) {
          this.shapes[i].pathsData.length = 0
        }
      }
    } else if (!((e === 1 && s === 0) || (e === 0 && s === 1))) {
      var segments = []
      var shapeData
      var localShapeCollection
      for (i = 0; i < len; i += 1) {
        shapeData = this.shapes[i]
        // if shape hasn't changed and trim properties haven't changed, cached previous path can be used
        if (!shapeData.shape._mdf && !this._mdf && !_isFirstFrame && this.m !== 2) {
          shapeData.shape.paths = shapeData.localShapeCollection
        } else {
          shapePaths = shapeData.shape.paths
          jLen = shapePaths._length
          totalShapeLength = 0
          if (!shapeData.shape._mdf && shapeData.pathsData.length) {
            totalShapeLength = shapeData.totalShapeLength
          } else {
            pathsData = this.releasePathsData(shapeData.pathsData)
            for (j = 0; j < jLen; j += 1) {
              pathData = bez.getSegmentsLength(shapePaths.shapes[j])
              pathsData.push(pathData)
              totalShapeLength += pathData.totalLength
            }
            shapeData.totalShapeLength = totalShapeLength
            shapeData.pathsData = pathsData
          }

          totalModifierLength += totalShapeLength
          shapeData.shape._mdf = true
        }
      }
      var shapeS = s
      var shapeE = e
      var addedLength = 0
      var edges
      for (i = len - 1; i >= 0; i -= 1) {
        shapeData = this.shapes[i]
        if (shapeData.shape._mdf) {
          localShapeCollection = shapeData.localShapeCollection
          localShapeCollection.releaseShapes()
          // if m === 2 means paths are trimmed individually so edges need to be found for this specific shape relative to whoel group
          if (this.m === 2 && len > 1) {
            edges = this.calculateShapeEdges(s, e, shapeData.totalShapeLength, addedLength, totalModifierLength)
            addedLength += shapeData.totalShapeLength
          } else {
            edges = [[shapeS, shapeE]]
          }
          jLen = edges.length
          for (j = 0; j < jLen; j += 1) {
            shapeS = edges[j][0]
            shapeE = edges[j][1]
            segments.length = 0
            if (shapeE <= 1) {
              segments.push({
                s: shapeData.totalShapeLength * shapeS,
                e: shapeData.totalShapeLength * shapeE,
              })
            } else if (shapeS >= 1) {
              segments.push({
                s: shapeData.totalShapeLength * (shapeS - 1),
                e: shapeData.totalShapeLength * (shapeE - 1),
              })
            } else {
              segments.push({
                s: shapeData.totalShapeLength * shapeS,
                e: shapeData.totalShapeLength,
              })
              segments.push({
                s: 0,
                e: shapeData.totalShapeLength * (shapeE - 1),
              })
            }
            var newShapesData = this.addShapes(shapeData, segments[0])
            if (segments[0].s !== segments[0].e) {
              if (segments.length > 1) {
                var lastShapeInCollection = shapeData.shape.paths.shapes[shapeData.shape.paths._length - 1]
                if (lastShapeInCollection.c) {
                  var lastShape = newShapesData.pop()
                  this.addPaths(newShapesData, localShapeCollection)
                  newShapesData = this.addShapes(shapeData, segments[1], lastShape)
                } else {
                  this.addPaths(newShapesData, localShapeCollection)
                  newShapesData = this.addShapes(shapeData, segments[1])
                }
              }
              this.addPaths(newShapesData, localShapeCollection)
            }
          }
          shapeData.shape.paths = localShapeCollection
        }
      }
    } else if (this._mdf) {
      for (i = 0; i < len; i += 1) {
        // Releasign Trim Cached paths data when no trim applied in case shapes are modified inbetween.
        // Don't remove this even if it's losing cached info.
        this.shapes[i].pathsData.length = 0
        this.shapes[i].shape._mdf = true
      }
    }
  }

  TrimModifier.prototype.addPaths = function (newPaths, localShapeCollection) {
    var i
    var len = newPaths.length
    for (i = 0; i < len; i += 1) {
      localShapeCollection.addShape(newPaths[i])
    }
  }

  TrimModifier.prototype.addSegment = function (pt1, pt2, pt3, pt4, shapePath, pos, newShape) {
    shapePath.setXYAt(pt2[0], pt2[1], 'o', pos)
    shapePath.setXYAt(pt3[0], pt3[1], 'i', pos + 1)
    if (newShape) {
      shapePath.setXYAt(pt1[0], pt1[1], 'v', pos)
    }
    shapePath.setXYAt(pt4[0], pt4[1], 'v', pos + 1)
  }

  TrimModifier.prototype.addSegmentFromArray = function (points, shapePath, pos, newShape) {
    shapePath.setXYAt(points[1], points[5], 'o', pos)
    shapePath.setXYAt(points[2], points[6], 'i', pos + 1)
    if (newShape) {
      shapePath.setXYAt(points[0], points[4], 'v', pos)
    }
    shapePath.setXYAt(points[3], points[7], 'v', pos + 1)
  }

  TrimModifier.prototype.addShapes = function (shapeData, shapeSegment, shapePath) {
    var pathsData = shapeData.pathsData
    var shapePaths = shapeData.shape.paths.shapes
    var i
    var len = shapeData.shape.paths._length
    var j
    var jLen
    var addedLength = 0
    var currentLengthData
    var segmentCount
    var lengths
    var segment
    var shapes = []
    var initPos
    var newShape = true
    if (!shapePath) {
      shapePath = shapePool.newElement()
      segmentCount = 0
      initPos = 0
    } else {
      segmentCount = shapePath._length
      initPos = shapePath._length
    }
    shapes.push(shapePath)
    for (i = 0; i < len; i += 1) {
      lengths = pathsData[i].lengths
      shapePath.c = shapePaths[i].c
      jLen = shapePaths[i].c ? lengths.length : lengths.length + 1
      for (j = 1; j < jLen; j += 1) {
        currentLengthData = lengths[j - 1]
        if (addedLength + currentLengthData.addedLength < shapeSegment.s) {
          addedLength += currentLengthData.addedLength
          shapePath.c = false
        } else if (addedLength > shapeSegment.e) {
          shapePath.c = false
          break
        } else {
          if (shapeSegment.s <= addedLength && shapeSegment.e >= addedLength + currentLengthData.addedLength) {
            this.addSegment(
              shapePaths[i].v[j - 1],
              shapePaths[i].o[j - 1],
              shapePaths[i].i[j],
              shapePaths[i].v[j],
              shapePath,
              segmentCount,
              newShape,
            )
            newShape = false
          } else {
            segment = bez.getNewSegment(
              shapePaths[i].v[j - 1],
              shapePaths[i].v[j],
              shapePaths[i].o[j - 1],
              shapePaths[i].i[j],
              (shapeSegment.s - addedLength) / currentLengthData.addedLength,
              (shapeSegment.e - addedLength) / currentLengthData.addedLength,
              lengths[j - 1],
            )
            this.addSegmentFromArray(segment, shapePath, segmentCount, newShape)
            // this.addSegment(segment.pt1, segment.pt3, segment.pt4, segment.pt2, shapePath, segmentCount, newShape);
            newShape = false
            shapePath.c = false
          }
          addedLength += currentLengthData.addedLength
          segmentCount += 1
        }
      }
      if (shapePaths[i].c && lengths.length) {
        currentLengthData = lengths[j - 1]
        if (addedLength <= shapeSegment.e) {
          var segmentLength = lengths[j - 1].addedLength
          if (shapeSegment.s <= addedLength && shapeSegment.e >= addedLength + segmentLength) {
            this.addSegment(
              shapePaths[i].v[j - 1],
              shapePaths[i].o[j - 1],
              shapePaths[i].i[0],
              shapePaths[i].v[0],
              shapePath,
              segmentCount,
              newShape,
            )
            newShape = false
          } else {
            segment = bez.getNewSegment(
              shapePaths[i].v[j - 1],
              shapePaths[i].v[0],
              shapePaths[i].o[j - 1],
              shapePaths[i].i[0],
              (shapeSegment.s - addedLength) / segmentLength,
              (shapeSegment.e - addedLength) / segmentLength,
              lengths[j - 1],
            )
            this.addSegmentFromArray(segment, shapePath, segmentCount, newShape)
            // this.addSegment(segment.pt1, segment.pt3, segment.pt4, segment.pt2, shapePath, segmentCount, newShape);
            newShape = false
            shapePath.c = false
          }
        } else {
          shapePath.c = false
        }
        addedLength += currentLengthData.addedLength
        segmentCount += 1
      }
      if (shapePath._length) {
        shapePath.setXYAt(shapePath.v[initPos][0], shapePath.v[initPos][1], 'i', initPos)
        shapePath.setXYAt(
          shapePath.v[shapePath._length - 1][0],
          shapePath.v[shapePath._length - 1][1],
          'o',
          shapePath._length - 1,
        )
      }
      if (addedLength > shapeSegment.e) {
        break
      }
      if (i < len - 1) {
        shapePath = shapePool.newElement()
        newShape = true
        shapes.push(shapePath)
        segmentCount = 0
      }
    }
    return shapes
  }

  function PuckerAndBloatModifier() {}
  extendPrototype([ShapeModifier], PuckerAndBloatModifier)
  PuckerAndBloatModifier.prototype.initModifierProperties = function (elem, data) {
    this.getValue = this.processKeys
    this.amount = PropertyFactory.getProp(elem, data.a, 0, null, this)
    this._isAnimated = !!this.amount.effectsSequence.length
  }

  PuckerAndBloatModifier.prototype.processPath = function (path, amount) {
    var percent = amount / 100
    var centerPoint = [0, 0]
    var pathLength = path._length
    var i = 0
    for (i = 0; i < pathLength; i += 1) {
      centerPoint[0] += path.v[i][0]
      centerPoint[1] += path.v[i][1]
    }
    centerPoint[0] /= pathLength
    centerPoint[1] /= pathLength
    var clonedPath = shapePool.newElement()
    clonedPath.c = path.c
    var vX
    var vY
    var oX
    var oY
    var iX
    var iY
    for (i = 0; i < pathLength; i += 1) {
      vX = path.v[i][0] + (centerPoint[0] - path.v[i][0]) * percent
      vY = path.v[i][1] + (centerPoint[1] - path.v[i][1]) * percent
      oX = path.o[i][0] + (centerPoint[0] - path.o[i][0]) * -percent
      oY = path.o[i][1] + (centerPoint[1] - path.o[i][1]) * -percent
      iX = path.i[i][0] + (centerPoint[0] - path.i[i][0]) * -percent
      iY = path.i[i][1] + (centerPoint[1] - path.i[i][1]) * -percent
      clonedPath.setTripleAt(vX, vY, oX, oY, iX, iY, i)
    }
    return clonedPath
  }

  PuckerAndBloatModifier.prototype.processShapes = function (_isFirstFrame) {
    var shapePaths
    var i
    var len = this.shapes.length
    var j
    var jLen
    var amount = this.amount.v

    if (amount !== 0) {
      var shapeData
      var localShapeCollection
      for (i = 0; i < len; i += 1) {
        shapeData = this.shapes[i]
        localShapeCollection = shapeData.localShapeCollection
        if (!(!shapeData.shape._mdf && !this._mdf && !_isFirstFrame)) {
          localShapeCollection.releaseShapes()
          shapeData.shape._mdf = true
          shapePaths = shapeData.shape.paths.shapes
          jLen = shapeData.shape.paths._length
          for (j = 0; j < jLen; j += 1) {
            localShapeCollection.addShape(this.processPath(shapePaths[j], amount))
          }
        }
        shapeData.shape.paths = shapeData.localShapeCollection
      }
    }
    if (!this.dynamicProperties.length) {
      this._mdf = false
    }
  }

  const TransformPropertyFactory = (function () {
    var defaultVector = [0, 0]

    function applyToMatrix(mat) {
      var _mdf = this._mdf
      this.iterateDynamicProperties()
      this._mdf = this._mdf || _mdf
      if (this.a) {
        mat.translate(-this.a.v[0], -this.a.v[1], this.a.v[2])
      }
      if (this.s) {
        mat.scale(this.s.v[0], this.s.v[1], this.s.v[2])
      }
      if (this.sk) {
        mat.skewFromAxis(-this.sk.v, this.sa.v)
      }
      if (this.r) {
        mat.rotate(-this.r.v)
      } else {
        mat
          .rotateZ(-this.rz.v)
          .rotateY(this.ry.v)
          .rotateX(this.rx.v)
          .rotateZ(-this.or.v[2])
          .rotateY(this.or.v[1])
          .rotateX(this.or.v[0])
      }
      if (this.data.p.s) {
        if (this.data.p.z) {
          mat.translate(this.px.v, this.py.v, -this.pz.v)
        } else {
          mat.translate(this.px.v, this.py.v, 0)
        }
      } else {
        mat.translate(this.p.v[0], this.p.v[1], -this.p.v[2])
      }
    }
    function processKeys(forceRender) {
      if (this.elem.globalData.frameId === this.frameId) {
        return
      }
      if (this._isDirty) {
        this.precalculateMatrix()
        this._isDirty = false
      }

      this.iterateDynamicProperties()

      if (this._mdf || forceRender) {
        var frameRate
        this.v.cloneFromProps(this.pre.props)
        if (this.appliedTransformations < 1) {
          this.v.translate(-this.a.v[0], -this.a.v[1], this.a.v[2])
        }
        if (this.appliedTransformations < 2) {
          this.v.scale(this.s.v[0], this.s.v[1], this.s.v[2])
        }
        if (this.sk && this.appliedTransformations < 3) {
          this.v.skewFromAxis(-this.sk.v, this.sa.v)
        }
        if (this.r && this.appliedTransformations < 4) {
          this.v.rotate(-this.r.v)
        } else if (!this.r && this.appliedTransformations < 4) {
          this.v
            .rotateZ(-this.rz.v)
            .rotateY(this.ry.v)
            .rotateX(this.rx.v)
            .rotateZ(-this.or.v[2])
            .rotateY(this.or.v[1])
            .rotateX(this.or.v[0])
        }
        if (this.autoOriented) {
          var v1
          var v2
          frameRate = this.elem.globalData.frameRate
          if (this.p && this.p.keyframes && this.p.getValueAtTime) {
            if (this.p._caching.lastFrame + this.p.offsetTime <= this.p.keyframes[0].t) {
              v1 = this.p.getValueAtTime((this.p.keyframes[0].t + 0.01) / frameRate, 0)
              v2 = this.p.getValueAtTime(this.p.keyframes[0].t / frameRate, 0)
            } else if (
              this.p._caching.lastFrame + this.p.offsetTime >=
              this.p.keyframes[this.p.keyframes.length - 1].t
            ) {
              v1 = this.p.getValueAtTime(this.p.keyframes[this.p.keyframes.length - 1].t / frameRate, 0)
              v2 = this.p.getValueAtTime((this.p.keyframes[this.p.keyframes.length - 1].t - 0.05) / frameRate, 0)
            } else {
              v1 = this.p.pv
              v2 = this.p.getValueAtTime(
                (this.p._caching.lastFrame + this.p.offsetTime - 0.01) / frameRate,
                this.p.offsetTime,
              )
            }
          } else if (
            this.px &&
            this.px.keyframes &&
            this.py.keyframes &&
            this.px.getValueAtTime &&
            this.py.getValueAtTime
          ) {
            v1 = []
            v2 = []
            var px = this.px
            var py = this.py
            if (px._caching.lastFrame + px.offsetTime <= px.keyframes[0].t) {
              v1[0] = px.getValueAtTime((px.keyframes[0].t + 0.01) / frameRate, 0)
              v1[1] = py.getValueAtTime((py.keyframes[0].t + 0.01) / frameRate, 0)
              v2[0] = px.getValueAtTime(px.keyframes[0].t / frameRate, 0)
              v2[1] = py.getValueAtTime(py.keyframes[0].t / frameRate, 0)
            } else if (px._caching.lastFrame + px.offsetTime >= px.keyframes[px.keyframes.length - 1].t) {
              v1[0] = px.getValueAtTime(px.keyframes[px.keyframes.length - 1].t / frameRate, 0)
              v1[1] = py.getValueAtTime(py.keyframes[py.keyframes.length - 1].t / frameRate, 0)
              v2[0] = px.getValueAtTime((px.keyframes[px.keyframes.length - 1].t - 0.01) / frameRate, 0)
              v2[1] = py.getValueAtTime((py.keyframes[py.keyframes.length - 1].t - 0.01) / frameRate, 0)
            } else {
              v1 = [px.pv, py.pv]
              v2[0] = px.getValueAtTime((px._caching.lastFrame + px.offsetTime - 0.01) / frameRate, px.offsetTime)
              v2[1] = py.getValueAtTime((py._caching.lastFrame + py.offsetTime - 0.01) / frameRate, py.offsetTime)
            }
          } else {
            v2 = defaultVector
            v1 = v2
          }
          this.v.rotate(-Math.atan2(v1[1] - v2[1], v1[0] - v2[0]))
        }
        if (this.data.p && this.data.p.s) {
          if (this.data.p.z) {
            this.v.translate(this.px.v, this.py.v, -this.pz.v)
          } else {
            this.v.translate(this.px.v, this.py.v, 0)
          }
        } else {
          this.v.translate(this.p.v[0], this.p.v[1], -this.p.v[2])
        }
      }
      this.frameId = this.elem.globalData.frameId
    }

    function precalculateMatrix() {
      if (!this.a.k) {
        this.pre.translate(-this.a.v[0], -this.a.v[1], this.a.v[2])
        this.appliedTransformations = 1
      } else {
        return
      }
      if (!this.s.effectsSequence.length) {
        this.pre.scale(this.s.v[0], this.s.v[1], this.s.v[2])
        this.appliedTransformations = 2
      } else {
        return
      }
      if (this.sk) {
        if (!this.sk.effectsSequence.length && !this.sa.effectsSequence.length) {
          this.pre.skewFromAxis(-this.sk.v, this.sa.v)
          this.appliedTransformations = 3
        } else {
          return
        }
      }
      if (this.r) {
        if (!this.r.effectsSequence.length) {
          this.pre.rotate(-this.r.v)
          this.appliedTransformations = 4
        }
      } else if (
        !this.rz.effectsSequence.length &&
        !this.ry.effectsSequence.length &&
        !this.rx.effectsSequence.length &&
        !this.or.effectsSequence.length
      ) {
        this.pre
          .rotateZ(-this.rz.v)
          .rotateY(this.ry.v)
          .rotateX(this.rx.v)
          .rotateZ(-this.or.v[2])
          .rotateY(this.or.v[1])
          .rotateX(this.or.v[0])
        this.appliedTransformations = 4
      }
    }

    function autoOrient() {
      //
      // var prevP = this.getValueAtTime();
    }

    function addDynamicProperty(prop) {
      this._addDynamicProperty(prop)
      this.elem.addDynamicProperty(prop)
      this._isDirty = true
    }

    function TransformProperty(elem, data, container) {
      this.elem = elem
      this.frameId = -1
      this.propType = 'transform'
      this.data = data
      this.v = new Matrix()
      // Precalculated matrix with non animated properties
      this.pre = new Matrix()
      this.appliedTransformations = 0
      this.initDynamicPropertyContainer(container || elem)
      if (data.p && data.p.s) {
        this.px = PropertyFactory.getProp(elem, data.p.x, 0, 0, this)
        this.py = PropertyFactory.getProp(elem, data.p.y, 0, 0, this)
        if (data.p.z) {
          this.pz = PropertyFactory.getProp(elem, data.p.z, 0, 0, this)
        }
      } else {
        this.p = PropertyFactory.getProp(elem, data.p || { k: [0, 0, 0] }, 1, 0, this)
      }
      if (data.rx) {
        this.rx = PropertyFactory.getProp(elem, data.rx, 0, degToRads, this)
        this.ry = PropertyFactory.getProp(elem, data.ry, 0, degToRads, this)
        this.rz = PropertyFactory.getProp(elem, data.rz, 0, degToRads, this)
        if (data.or.k[0].ti) {
          var i
          var len = data.or.k.length
          for (i = 0; i < len; i += 1) {
            data.or.k[i].to = null
            data.or.k[i].ti = null
          }
        }
        this.or = PropertyFactory.getProp(elem, data.or, 1, degToRads, this)
        // sh Indicates it needs to be capped between -180 and 180
        this.or.sh = true
      } else {
        this.r = PropertyFactory.getProp(elem, data.r || { k: 0 }, 0, degToRads, this)
      }
      if (data.sk) {
        this.sk = PropertyFactory.getProp(elem, data.sk, 0, degToRads, this)
        this.sa = PropertyFactory.getProp(elem, data.sa, 0, degToRads, this)
      }
      this.a = PropertyFactory.getProp(elem, data.a || { k: [0, 0, 0] }, 1, 0, this)
      this.s = PropertyFactory.getProp(elem, data.s || { k: [100, 100, 100] }, 1, 0.01, this)
      // Opacity is not part of the transform properties, that's why it won't use this.dynamicProperties. That way transforms won't get updated if opacity changes.
      if (data.o) {
        this.o = PropertyFactory.getProp(elem, data.o, 0, 0.01, elem)
      } else {
        this.o = { _mdf: false, v: 1 }
      }
      this._isDirty = true
      if (!this.dynamicProperties.length) {
        this.getValue(true)
      }
    }

    TransformProperty.prototype = {
      applyToMatrix: applyToMatrix,
      getValue: processKeys,
      precalculateMatrix: precalculateMatrix,
      autoOrient: autoOrient,
    }

    extendPrototype([DynamicPropertyContainer], TransformProperty)
    TransformProperty.prototype.addDynamicProperty = addDynamicProperty
    TransformProperty.prototype._addDynamicProperty = DynamicPropertyContainer.prototype.addDynamicProperty

    function getTransformProperty(elem, data, container) {
      return new TransformProperty(elem, data, container)
    }

    return {
      getTransformProperty: getTransformProperty,
    }
  })()

  function RepeaterModifier() {}
  extendPrototype([ShapeModifier], RepeaterModifier)

  RepeaterModifier.prototype.initModifierProperties = function (elem, data) {
    this.getValue = this.processKeys
    this.c = PropertyFactory.getProp(elem, data.c, 0, null, this)
    this.o = PropertyFactory.getProp(elem, data.o, 0, null, this)
    this.tr = TransformPropertyFactory.getTransformProperty(elem, data.tr, this)
    this.so = PropertyFactory.getProp(elem, data.tr.so, 0, 0.01, this)
    this.eo = PropertyFactory.getProp(elem, data.tr.eo, 0, 0.01, this)
    this.data = data
    if (!this.dynamicProperties.length) {
      this.getValue(true)
    }
    this._isAnimated = !!this.dynamicProperties.length
    this.pMatrix = new Matrix()
    this.rMatrix = new Matrix()
    this.sMatrix = new Matrix()
    this.tMatrix = new Matrix()
    this.matrix = new Matrix()
  }

  RepeaterModifier.prototype.applyTransforms = function (pMatrix, rMatrix, sMatrix, transform, perc, inv) {
    var dir = inv ? -1 : 1
    var scaleX = transform.s.v[0] + (1 - transform.s.v[0]) * (1 - perc)
    var scaleY = transform.s.v[1] + (1 - transform.s.v[1]) * (1 - perc)
    pMatrix.translate(transform.p.v[0] * dir * perc, transform.p.v[1] * dir * perc, transform.p.v[2])
    rMatrix.translate(-transform.a.v[0], -transform.a.v[1], transform.a.v[2])
    rMatrix.rotate(-transform.r.v * dir * perc)
    rMatrix.translate(transform.a.v[0], transform.a.v[1], transform.a.v[2])
    sMatrix.translate(-transform.a.v[0], -transform.a.v[1], transform.a.v[2])
    sMatrix.scale(inv ? 1 / scaleX : scaleX, inv ? 1 / scaleY : scaleY)
    sMatrix.translate(transform.a.v[0], transform.a.v[1], transform.a.v[2])
  }

  RepeaterModifier.prototype.init = function (elem, arr, pos, elemsData) {
    this.elem = elem
    this.arr = arr
    this.pos = pos
    this.elemsData = elemsData
    this._currentCopies = 0
    this._elements = []
    this._groups = []
    this.frameId = -1
    this.initDynamicPropertyContainer(elem)
    this.initModifierProperties(elem, arr[pos])
    while (pos > 0) {
      pos -= 1
      // this._elements.unshift(arr.splice(pos,1)[0]);
      this._elements.unshift(arr[pos])
    }
    if (this.dynamicProperties.length) {
      this.k = true
    } else {
      this.getValue(true)
    }
  }

  RepeaterModifier.prototype.resetElements = function (elements) {
    var i
    var len = elements.length
    for (i = 0; i < len; i += 1) {
      elements[i]._processed = false
      if (elements[i].ty === 'gr') {
        this.resetElements(elements[i].it)
      }
    }
  }

  RepeaterModifier.prototype.cloneElements = function (elements) {
    var newElements = JSON.parse(JSON.stringify(elements))
    this.resetElements(newElements)
    return newElements
  }

  RepeaterModifier.prototype.changeGroupRender = function (elements, renderFlag) {
    var i
    var len = elements.length
    for (i = 0; i < len; i += 1) {
      elements[i]._render = renderFlag
      if (elements[i].ty === 'gr') {
        this.changeGroupRender(elements[i].it, renderFlag)
      }
    }
  }

  RepeaterModifier.prototype.processShapes = function (_isFirstFrame) {
    var items
    var itemsTransform
    var i
    var dir
    var cont
    var hasReloaded = false
    if (this._mdf || _isFirstFrame) {
      var copies = Math.ceil(this.c.v)
      if (this._groups.length < copies) {
        while (this._groups.length < copies) {
          var group = {
            it: this.cloneElements(this._elements),
            ty: 'gr',
          }
          group.it.push({
            a: { a: 0, ix: 1, k: [0, 0] },
            nm: 'Transform',
            o: { a: 0, ix: 7, k: 100 },
            p: { a: 0, ix: 2, k: [0, 0] },
            r: {
              a: 1,
              ix: 6,
              k: [
                { s: 0, e: 0, t: 0 },
                { s: 0, e: 0, t: 1 },
              ],
            },
            s: { a: 0, ix: 3, k: [100, 100] },
            sa: { a: 0, ix: 5, k: 0 },
            sk: { a: 0, ix: 4, k: 0 },
            ty: 'tr',
          })

          this.arr.splice(0, 0, group)
          this._groups.splice(0, 0, group)
          this._currentCopies += 1
        }
        this.elem.reloadShapes()
        hasReloaded = true
      }
      cont = 0
      var renderFlag
      for (i = 0; i <= this._groups.length - 1; i += 1) {
        renderFlag = cont < copies
        this._groups[i]._render = renderFlag
        this.changeGroupRender(this._groups[i].it, renderFlag)
        if (!renderFlag) {
          var elems = this.elemsData[i].it
          var transformData = elems[elems.length - 1]
          if (transformData.transform.op.v !== 0) {
            transformData.transform.op._mdf = true
            transformData.transform.op.v = 0
          } else {
            transformData.transform.op._mdf = false
          }
        }
        cont += 1
      }

      this._currentCopies = copies
      /// /

      var offset = this.o.v
      var offsetModulo = offset % 1
      var roundOffset = offset > 0 ? Math.floor(offset) : Math.ceil(offset)
      var pProps = this.pMatrix.props
      var rProps = this.rMatrix.props
      var sProps = this.sMatrix.props
      this.pMatrix.reset()
      this.rMatrix.reset()
      this.sMatrix.reset()
      this.tMatrix.reset()
      this.matrix.reset()
      var iteration = 0

      if (offset > 0) {
        while (iteration < roundOffset) {
          this.applyTransforms(this.pMatrix, this.rMatrix, this.sMatrix, this.tr, 1, false)
          iteration += 1
        }
        if (offsetModulo) {
          this.applyTransforms(this.pMatrix, this.rMatrix, this.sMatrix, this.tr, offsetModulo, false)
          iteration += offsetModulo
        }
      } else if (offset < 0) {
        while (iteration > roundOffset) {
          this.applyTransforms(this.pMatrix, this.rMatrix, this.sMatrix, this.tr, 1, true)
          iteration -= 1
        }
        if (offsetModulo) {
          this.applyTransforms(this.pMatrix, this.rMatrix, this.sMatrix, this.tr, -offsetModulo, true)
          iteration -= offsetModulo
        }
      }
      i = this.data.m === 1 ? 0 : this._currentCopies - 1
      dir = this.data.m === 1 ? 1 : -1
      cont = this._currentCopies
      var j
      var jLen
      while (cont) {
        items = this.elemsData[i].it
        itemsTransform = items[items.length - 1].transform.mProps.v.props
        jLen = itemsTransform.length
        items[items.length - 1].transform.mProps._mdf = true
        items[items.length - 1].transform.op._mdf = true
        items[items.length - 1].transform.op.v =
          this._currentCopies === 1 ? this.so.v : this.so.v + (this.eo.v - this.so.v) * (i / (this._currentCopies - 1))

        if (iteration !== 0) {
          if ((i !== 0 && dir === 1) || (i !== this._currentCopies - 1 && dir === -1)) {
            this.applyTransforms(this.pMatrix, this.rMatrix, this.sMatrix, this.tr, 1, false)
          }
          this.matrix.transform(
            rProps[0],
            rProps[1],
            rProps[2],
            rProps[3],
            rProps[4],
            rProps[5],
            rProps[6],
            rProps[7],
            rProps[8],
            rProps[9],
            rProps[10],
            rProps[11],
            rProps[12],
            rProps[13],
            rProps[14],
            rProps[15],
          )
          this.matrix.transform(
            sProps[0],
            sProps[1],
            sProps[2],
            sProps[3],
            sProps[4],
            sProps[5],
            sProps[6],
            sProps[7],
            sProps[8],
            sProps[9],
            sProps[10],
            sProps[11],
            sProps[12],
            sProps[13],
            sProps[14],
            sProps[15],
          )
          this.matrix.transform(
            pProps[0],
            pProps[1],
            pProps[2],
            pProps[3],
            pProps[4],
            pProps[5],
            pProps[6],
            pProps[7],
            pProps[8],
            pProps[9],
            pProps[10],
            pProps[11],
            pProps[12],
            pProps[13],
            pProps[14],
            pProps[15],
          )

          for (j = 0; j < jLen; j += 1) {
            itemsTransform[j] = this.matrix.props[j]
          }
          this.matrix.reset()
        } else {
          this.matrix.reset()
          for (j = 0; j < jLen; j += 1) {
            itemsTransform[j] = this.matrix.props[j]
          }
        }
        iteration += 1
        cont -= 1
        i += dir
      }
    } else {
      cont = this._currentCopies
      i = 0
      dir = 1
      while (cont) {
        items = this.elemsData[i].it
        itemsTransform = items[items.length - 1].transform.mProps.v.props
        items[items.length - 1].transform.mProps._mdf = false
        items[items.length - 1].transform.op._mdf = false
        cont -= 1
        i += dir
      }
    }
    return hasReloaded
  }

  RepeaterModifier.prototype.addShape = function () {}

  function RoundCornersModifier() {}
  extendPrototype([ShapeModifier], RoundCornersModifier)
  RoundCornersModifier.prototype.initModifierProperties = function (elem, data) {
    this.getValue = this.processKeys
    this.rd = PropertyFactory.getProp(elem, data.r, 0, null, this)
    this._isAnimated = !!this.rd.effectsSequence.length
  }

  RoundCornersModifier.prototype.processPath = function (path, round) {
    var clonedPath = shapePool.newElement()
    clonedPath.c = path.c
    var i
    var len = path._length
    var currentV
    var currentI
    var currentO
    var closerV
    var distance
    var newPosPerc
    var index = 0
    var vX
    var vY
    var oX
    var oY
    var iX
    var iY
    for (i = 0; i < len; i += 1) {
      currentV = path.v[i]
      currentO = path.o[i]
      currentI = path.i[i]
      if (
        currentV[0] === currentO[0] &&
        currentV[1] === currentO[1] &&
        currentV[0] === currentI[0] &&
        currentV[1] === currentI[1]
      ) {
        if ((i === 0 || i === len - 1) && !path.c) {
          clonedPath.setTripleAt(currentV[0], currentV[1], currentO[0], currentO[1], currentI[0], currentI[1], index)
          /* clonedPath.v[index] = currentV;
                clonedPath.o[index] = currentO;
                clonedPath.i[index] = currentI; */
          index += 1
        } else {
          if (i === 0) {
            closerV = path.v[len - 1]
          } else {
            closerV = path.v[i - 1]
          }
          distance = Math.sqrt(Math.pow(currentV[0] - closerV[0], 2) + Math.pow(currentV[1] - closerV[1], 2))
          newPosPerc = distance ? Math.min(distance / 2, round) / distance : 0
          iX = currentV[0] + (closerV[0] - currentV[0]) * newPosPerc
          vX = iX
          iY = currentV[1] - (currentV[1] - closerV[1]) * newPosPerc
          vY = iY
          oX = vX - (vX - currentV[0]) * roundCorner
          oY = vY - (vY - currentV[1]) * roundCorner
          clonedPath.setTripleAt(vX, vY, oX, oY, iX, iY, index)
          index += 1

          if (i === len - 1) {
            closerV = path.v[0]
          } else {
            closerV = path.v[i + 1]
          }
          distance = Math.sqrt(Math.pow(currentV[0] - closerV[0], 2) + Math.pow(currentV[1] - closerV[1], 2))
          newPosPerc = distance ? Math.min(distance / 2, round) / distance : 0
          oX = currentV[0] + (closerV[0] - currentV[0]) * newPosPerc
          vX = oX
          oY = currentV[1] + (closerV[1] - currentV[1]) * newPosPerc
          vY = oY
          iX = vX - (vX - currentV[0]) * roundCorner
          iY = vY - (vY - currentV[1]) * roundCorner
          clonedPath.setTripleAt(vX, vY, oX, oY, iX, iY, index)
          index += 1
        }
      } else {
        clonedPath.setTripleAt(
          path.v[i][0],
          path.v[i][1],
          path.o[i][0],
          path.o[i][1],
          path.i[i][0],
          path.i[i][1],
          index,
        )
        index += 1
      }
    }
    return clonedPath
  }

  RoundCornersModifier.prototype.processShapes = function (_isFirstFrame) {
    var shapePaths
    var i
    var len = this.shapes.length
    var j
    var jLen
    var rd = this.rd.v

    if (rd !== 0) {
      var shapeData
      var localShapeCollection
      for (i = 0; i < len; i += 1) {
        shapeData = this.shapes[i]
        localShapeCollection = shapeData.localShapeCollection
        if (!(!shapeData.shape._mdf && !this._mdf && !_isFirstFrame)) {
          localShapeCollection.releaseShapes()
          shapeData.shape._mdf = true
          shapePaths = shapeData.shape.paths.shapes
          jLen = shapeData.shape.paths._length
          for (j = 0; j < jLen; j += 1) {
            localShapeCollection.addShape(this.processPath(shapePaths[j], rd))
          }
        }
        shapeData.shape.paths = shapeData.localShapeCollection
      }
    }
    if (!this.dynamicProperties.length) {
      this._mdf = false
    }
  }

  function getFontProperties(fontData) {
    var styles = fontData.fStyle ? fontData.fStyle.split(' ') : []

    var fWeight = 'normal'
    var fStyle = 'normal'
    var len = styles.length
    var styleName
    for (var i = 0; i < len; i += 1) {
      styleName = styles[i].toLowerCase()
      switch (styleName) {
        case 'italic':
          fStyle = 'italic'
          break
        case 'bold':
          fWeight = '700'
          break
        case 'black':
          fWeight = '900'
          break
        case 'medium':
          fWeight = '500'
          break
        case 'regular':
        case 'normal':
          fWeight = '400'
          break
        case 'light':
        case 'thin':
          fWeight = '200'
          break
        default:
          break
      }
    }

    return {
      style: fStyle,
      weight: fontData.fWeight || fWeight,
    }
  }

  const FontManager = (function () {
    var maxWaitingTime = 5000
    var emptyChar = {
      w: 0,
      size: 0,
      shapes: [],
      data: {
        shapes: [],
      },
    }
    var combinedCharacters = []
    // Hindi characters
    combinedCharacters = combinedCharacters.concat([
      2304,
      2305,
      2306,
      2307,
      2362,
      2363,
      2364,
      2364,
      2366,
      2367,
      2368,
      2369,
      2370,
      2371,
      2372,
      2373,
      2374,
      2375,
      2376,
      2377,
      2378,
      2379,
      2380,
      2381,
      2382,
      2383,
      2387,
      2388,
      2389,
      2390,
      2391,
      2402,
      2403,
    ])

    var surrogateModifiers = ['d83cdffb', 'd83cdffc', 'd83cdffd', 'd83cdffe', 'd83cdfff']

    var zeroWidthJoiner = [65039, 8205]

    function trimFontOptions(font) {
      var familyArray = font.split(',')
      var i
      var len = familyArray.length
      var enabledFamilies = []
      for (i = 0; i < len; i += 1) {
        if (familyArray[i] !== 'sans-serif' && familyArray[i] !== 'monospace') {
          enabledFamilies.push(familyArray[i])
        }
      }
      return enabledFamilies.join(',')
    }

    function setUpNode(font, family) {
      var parentNode = createTag('span')
      // Node is invisible to screen readers.
      parentNode.setAttribute('aria-hidden', true)
      parentNode.style.fontFamily = family
      var node = createTag('span')
      // Characters that vary significantly among different fonts
      node.innerText = 'giItT1WQy@!-/#'
      // Visible - so we can measure it - but not on the screen
      parentNode.style.position = 'absolute'
      parentNode.style.left = '-10000px'
      parentNode.style.top = '-10000px'
      // Large font size makes even subtle changes obvious
      parentNode.style.fontSize = '300px'
      // Reset any font properties
      parentNode.style.fontVariant = 'normal'
      parentNode.style.fontStyle = 'normal'
      parentNode.style.fontWeight = 'normal'
      parentNode.style.letterSpacing = '0'
      parentNode.appendChild(node)
      document.body.appendChild(parentNode)

      // Remember width with no applied web font
      var width = node.offsetWidth
      node.style.fontFamily = trimFontOptions(font) + ', ' + family
      return { node: node, w: width, parent: parentNode }
    }

    function checkLoadedFonts() {
      var i
      var len = this.fonts.length
      var node
      var w
      var loadedCount = len
      for (i = 0; i < len; i += 1) {
        if (this.fonts[i].loaded) {
          loadedCount -= 1
        } else if (this.fonts[i].fOrigin === 'n' || this.fonts[i].origin === 0) {
          this.fonts[i].loaded = true
        } else {
          node = this.fonts[i].monoCase.node
          w = this.fonts[i].monoCase.w
          if (node.offsetWidth !== w) {
            loadedCount -= 1
            this.fonts[i].loaded = true
          } else {
            node = this.fonts[i].sansCase.node
            w = this.fonts[i].sansCase.w
            if (node.offsetWidth !== w) {
              loadedCount -= 1
              this.fonts[i].loaded = true
            }
          }
          if (this.fonts[i].loaded) {
            this.fonts[i].sansCase.parent.parentNode.removeChild(this.fonts[i].sansCase.parent)
            this.fonts[i].monoCase.parent.parentNode.removeChild(this.fonts[i].monoCase.parent)
          }
        }
      }

      if (loadedCount !== 0 && Date.now() - this.initTime < maxWaitingTime) {
        setTimeout(this.checkLoadedFontsBinded, 20)
      } else {
        setTimeout(this.setIsLoadedBinded, 10)
      }
    }

    function createHelper(fontData, def) {
      var engine = document.body && def ? 'svg' : 'canvas'
      var helper
      var fontProps = getFontProperties(fontData)
      if (engine === 'svg') {
        var tHelper = createNS('text')
        tHelper.style.fontSize = '100px'
        // tHelper.style.fontFamily = fontData.fFamily;
        tHelper.setAttribute('font-family', fontData.fFamily)
        tHelper.setAttribute('font-style', fontProps.style)
        tHelper.setAttribute('font-weight', fontProps.weight)
        tHelper.textContent = '1'
        if (fontData.fClass) {
          tHelper.style.fontFamily = 'inherit'
          tHelper.setAttribute('class', fontData.fClass)
        } else {
          tHelper.style.fontFamily = fontData.fFamily
        }
        def.appendChild(tHelper)
        helper = tHelper
      } else {
        var tCanvasHelper = new OffscreenCanvas(500, 500).getContext('2d')
        tCanvasHelper.font = fontProps.style + ' ' + fontProps.weight + ' 100px ' + fontData.fFamily
        helper = tCanvasHelper
      }
      function measure(text) {
        if (engine === 'svg') {
          helper.textContent = text
          return helper.getComputedTextLength()
        }
        return helper.measureText(text).width
      }
      return {
        measureText: measure,
      }
    }

    function addFonts(fontData, defs) {
      if (!fontData) {
        this.isLoaded = true
        return
      }
      if (this.chars) {
        this.isLoaded = true
        this.fonts = fontData.list
        return
      }
      if (!document.body) {
        this.isLoaded = true
        fontData.list.forEach((data) => {
          data.helper = createHelper(data)
          data.cache = {}
        })
        this.fonts = fontData.list
        return
      }

      var fontArr = fontData.list
      var i
      var len = fontArr.length
      var _pendingFonts = len
      for (i = 0; i < len; i += 1) {
        var shouldLoadFont = true
        var loadedSelector
        var j
        fontArr[i].loaded = false
        fontArr[i].monoCase = setUpNode(fontArr[i].fFamily, 'monospace')
        fontArr[i].sansCase = setUpNode(fontArr[i].fFamily, 'sans-serif')
        if (!fontArr[i].fPath) {
          fontArr[i].loaded = true
          _pendingFonts -= 1
        } else if (fontArr[i].fOrigin === 'p' || fontArr[i].origin === 3) {
          loadedSelector = document.querySelectorAll(
            'style[f-forigin="p"][f-family="' +
              fontArr[i].fFamily +
              '"], style[f-origin="3"][f-family="' +
              fontArr[i].fFamily +
              '"]',
          )

          if (loadedSelector.length > 0) {
            shouldLoadFont = false
          }

          if (shouldLoadFont) {
            var s = createTag('style')
            s.setAttribute('f-forigin', fontArr[i].fOrigin)
            s.setAttribute('f-origin', fontArr[i].origin)
            s.setAttribute('f-family', fontArr[i].fFamily)
            s.type = 'text/css'
            s.innerText =
              '@font-face {font-family: ' +
              fontArr[i].fFamily +
              "; font-style: normal; src: url('" +
              fontArr[i].fPath +
              "');}"
            defs.appendChild(s)
          }
        } else if (fontArr[i].fOrigin === 'g' || fontArr[i].origin === 1) {
          loadedSelector = document.querySelectorAll('link[f-forigin="g"], link[f-origin="1"]')

          for (j = 0; j < loadedSelector.length; j += 1) {
            if (loadedSelector[j].href.indexOf(fontArr[i].fPath) !== -1) {
              // Font is already loaded
              shouldLoadFont = false
            }
          }

          if (shouldLoadFont) {
            var l = createTag('link')
            l.setAttribute('f-forigin', fontArr[i].fOrigin)
            l.setAttribute('f-origin', fontArr[i].origin)
            l.type = 'text/css'
            l.rel = 'stylesheet'
            l.href = fontArr[i].fPath
            document.body.appendChild(l)
          }
        } else if (fontArr[i].fOrigin === 't' || fontArr[i].origin === 2) {
          loadedSelector = document.querySelectorAll('script[f-forigin="t"], script[f-origin="2"]')

          for (j = 0; j < loadedSelector.length; j += 1) {
            if (fontArr[i].fPath === loadedSelector[j].src) {
              // Font is already loaded
              shouldLoadFont = false
            }
          }

          if (shouldLoadFont) {
            var sc = createTag('link')
            sc.setAttribute('f-forigin', fontArr[i].fOrigin)
            sc.setAttribute('f-origin', fontArr[i].origin)
            sc.setAttribute('rel', 'stylesheet')
            sc.setAttribute('href', fontArr[i].fPath)
            defs.appendChild(sc)
          }
        }
        fontArr[i].helper = createHelper(fontArr[i], defs)
        fontArr[i].cache = {}
        this.fonts.push(fontArr[i])
      }
      if (_pendingFonts === 0) {
        this.isLoaded = true
      } else {
        // On some cases even if the font is loaded, it won't load correctly when measuring text on canvas.
        // Adding this timeout seems to fix it
        setTimeout(this.checkLoadedFonts.bind(this), 100)
      }
    }

    function addChars(chars) {
      if (!chars) {
        return
      }
      if (!this.chars) {
        this.chars = []
      }
      var i
      var len = chars.length
      var j
      var jLen = this.chars.length
      var found
      for (i = 0; i < len; i += 1) {
        j = 0
        found = false
        while (j < jLen) {
          if (
            this.chars[j].style === chars[i].style &&
            this.chars[j].fFamily === chars[i].fFamily &&
            this.chars[j].ch === chars[i].ch
          ) {
            found = true
          }
          j += 1
        }
        if (!found) {
          this.chars.push(chars[i])
          jLen += 1
        }
      }
    }

    function getCharData(char, style, font) {
      var i = 0
      var len = this.chars.length
      while (i < len) {
        if (this.chars[i].ch === char && this.chars[i].style === style && this.chars[i].fFamily === font) {
          return this.chars[i]
        }
        i += 1
      }
      if (
        ((typeof char === 'string' && char.charCodeAt(0) !== 13) || !char) &&
        console &&
        console.warn && // eslint-disable-line no-console
        !this._warned
      ) {
        this._warned = true
        console.warn('Missing character from exported characters list: ', char, style, font) // eslint-disable-line no-console
      }
      return emptyChar
    }

    function measureText(char, fontName, size) {
      var fontData = this.getFontByName(fontName)
      var index = char.charCodeAt(0)
      if (!fontData.cache[index + 1]) {
        var tHelper = fontData.helper
        if (char === ' ') {
          var doubleSize = tHelper.measureText('|' + char + '|')
          var singleSize = tHelper.measureText('||')
          fontData.cache[index + 1] = (doubleSize - singleSize) / 100
        } else {
          fontData.cache[index + 1] = tHelper.measureText(char) / 100
        }
      }
      return fontData.cache[index + 1] * size
    }

    function getFontByName(name) {
      var i = 0
      var len = this.fonts.length
      while (i < len) {
        if (this.fonts[i].fName === name) {
          return this.fonts[i]
        }
        i += 1
      }
      return this.fonts[0]
    }

    function isModifier(firstCharCode, secondCharCode) {
      var sum = firstCharCode.toString(16) + secondCharCode.toString(16)
      return surrogateModifiers.indexOf(sum) !== -1
    }

    function isZeroWidthJoiner(firstCharCode, secondCharCode) {
      if (!secondCharCode) {
        return firstCharCode === zeroWidthJoiner[1]
      }
      return firstCharCode === zeroWidthJoiner[0] && secondCharCode === zeroWidthJoiner[1]
    }

    function isCombinedCharacter(char) {
      return combinedCharacters.indexOf(char) !== -1
    }

    function setIsLoaded() {
      this.isLoaded = true
    }

    var Font = function () {
      this.fonts = []
      this.chars = null
      this.typekitLoaded = 0
      this.isLoaded = false
      this._warned = false
      this.initTime = Date.now()
      this.setIsLoadedBinded = this.setIsLoaded.bind(this)
      this.checkLoadedFontsBinded = this.checkLoadedFonts.bind(this)
    }
    Font.isModifier = isModifier
    Font.isZeroWidthJoiner = isZeroWidthJoiner
    Font.isCombinedCharacter = isCombinedCharacter

    var fontPrototype = {
      addChars: addChars,
      addFonts: addFonts,
      getCharData: getCharData,
      getFontByName: getFontByName,
      measureText: measureText,
      checkLoadedFonts: checkLoadedFonts,
      setIsLoaded: setIsLoaded,
    }

    Font.prototype = fontPrototype

    return Font
  })()

  function RenderableElement() {}

  RenderableElement.prototype = {
    initRenderable: function () {
      // layer's visibility related to inpoint and outpoint. Rename isVisible to isInRange
      this.isInRange = false
      // layer's display state
      this.hidden = false
      // If layer's transparency equals 0, it can be hidden
      this.isTransparent = false
      // list of animated components
      this.renderableComponents = []
    },
    addRenderableComponent: function (component) {
      if (this.renderableComponents.indexOf(component) === -1) {
        this.renderableComponents.push(component)
      }
    },
    removeRenderableComponent: function (component) {
      if (this.renderableComponents.indexOf(component) !== -1) {
        this.renderableComponents.splice(this.renderableComponents.indexOf(component), 1)
      }
    },
    prepareRenderableFrame: function (num) {
      this.checkLayerLimits(num)
    },
    checkTransparency: function () {
      if (this.finalTransform.mProp.o.v <= 0) {
        if (!this.isTransparent && this.globalData.renderConfig.hideOnTransparent) {
          this.isTransparent = true
          this.hide()
        }
      } else if (this.isTransparent) {
        this.isTransparent = false
        this.show()
      }
    },
    /**
     * @function
     * Initializes frame related properties.
     *
     * @param {number} num
     * current frame number in Layer's time
     *
     */
    checkLayerLimits: function (num) {
      if (this.data.ip - this.data.st <= num && this.data.op - this.data.st > num) {
        if (this.isInRange !== true) {
          this.globalData._mdf = true
          this._mdf = true
          this.isInRange = true
          this.show()
        }
      } else if (this.isInRange !== false) {
        this.globalData._mdf = true
        this.isInRange = false
        this.hide()
      }
    },
    renderRenderable: function () {
      var i
      var len = this.renderableComponents.length
      for (i = 0; i < len; i += 1) {
        this.renderableComponents[i].renderFrame(this._isFirstFrame)
      }
      /* this.maskManager.renderFrame(this.finalTransform.mat);
        this.renderableEffectsManager.renderFrame(this._isFirstFrame); */
    },
    sourceRectAtTime: function () {
      return {
        top: 0,
        left: 0,
        width: 100,
        height: 100,
      }
    },
    getLayerSize: function () {
      if (this.data.ty === 5) {
        return { w: this.data.textData.width, h: this.data.textData.height }
      }
      return { w: this.data.width, h: this.data.height }
    },
  }

  const MaskManagerInterface = (function () {
    function MaskInterface(mask, data) {
      this._mask = mask
      this._data = data
    }
    Object.defineProperty(MaskInterface.prototype, 'maskPath', {
      get: function () {
        if (this._mask.prop.k) {
          this._mask.prop.getValue()
        }
        return this._mask.prop
      },
    })
    Object.defineProperty(MaskInterface.prototype, 'maskOpacity', {
      get: function () {
        if (this._mask.op.k) {
          this._mask.op.getValue()
        }
        return this._mask.op.v * 100
      },
    })

    var MaskManager = function (maskManager) {
      var _masksInterfaces = createSizedArray(maskManager.viewData.length)
      var i
      var len = maskManager.viewData.length
      for (i = 0; i < len; i += 1) {
        _masksInterfaces[i] = new MaskInterface(maskManager.viewData[i], maskManager.masksProperties[i])
      }

      var maskFunction = function (name) {
        i = 0
        while (i < len) {
          if (maskManager.masksProperties[i].nm === name) {
            return _masksInterfaces[i]
          }
          i += 1
        }
        return null
      }
      return maskFunction
    }
    return MaskManager
  })()

  const ExpressionPropertyInterface = (function () {
    var defaultUnidimensionalValue = { pv: 0, v: 0, mult: 1 }
    var defaultMultidimensionalValue = { pv: [0, 0, 0], v: [0, 0, 0], mult: 1 }

    function completeProperty(expressionValue, property, type) {
      Object.defineProperty(expressionValue, 'velocity', {
        get: function () {
          return property.getVelocityAtTime(property.comp.currentFrame)
        },
      })
      expressionValue.numKeys = property.keyframes ? property.keyframes.length : 0
      expressionValue.key = function (pos) {
        if (!expressionValue.numKeys) {
          return 0
        }
        var value = ''
        if ('s' in property.keyframes[pos - 1]) {
          value = property.keyframes[pos - 1].s
        } else if ('e' in property.keyframes[pos - 2]) {
          value = property.keyframes[pos - 2].e
        } else {
          value = property.keyframes[pos - 2].s
        }
        var valueProp = type === 'unidimensional' ? new Number(value) : Object.assign({}, value) // eslint-disable-line no-new-wrappers
        valueProp.time = property.keyframes[pos - 1].t / property.elem.comp.globalData.frameRate
        valueProp.value = type === 'unidimensional' ? value[0] : value
        return valueProp
      }
      expressionValue.valueAtTime = property.getValueAtTime
      expressionValue.speedAtTime = property.getSpeedAtTime
      expressionValue.velocityAtTime = property.getVelocityAtTime
      expressionValue.propertyGroup = property.propertyGroup
    }

    function UnidimensionalPropertyInterface(property) {
      if (!property || !('pv' in property)) {
        property = defaultUnidimensionalValue
      }
      var mult = 1 / property.mult
      var val = property.pv * mult
      var expressionValue = new Number(val) // eslint-disable-line no-new-wrappers
      expressionValue.value = val
      completeProperty(expressionValue, property, 'unidimensional')

      return function () {
        if (property.k) {
          property.getValue()
        }
        val = property.v * mult
        if (expressionValue.value !== val) {
          expressionValue = new Number(val) // eslint-disable-line no-new-wrappers
          expressionValue.value = val
          completeProperty(expressionValue, property, 'unidimensional')
        }
        return expressionValue
      }
    }

    function MultidimensionalPropertyInterface(property) {
      if (!property || !('pv' in property)) {
        property = defaultMultidimensionalValue
      }
      var mult = 1 / property.mult
      var len = (property.data && property.data.l) || property.pv.length
      var expressionValue = createTypedArray('float32', len)
      var arrValue = createTypedArray('float32', len)
      expressionValue.value = arrValue
      completeProperty(expressionValue, property, 'multidimensional')

      return function () {
        if (property.k) {
          property.getValue()
        }
        for (var i = 0; i < len; i += 1) {
          arrValue[i] = property.v[i] * mult
          expressionValue[i] = arrValue[i]
        }
        return expressionValue
      }
    }

    // TODO: try to avoid using this getter
    function defaultGetter() {
      return defaultUnidimensionalValue
    }

    return function (property) {
      if (!property) {
        return defaultGetter
      }
      if (property.propType === 'unidimensional') {
        return UnidimensionalPropertyInterface(property)
      }
      return MultidimensionalPropertyInterface(property)
    }
  })()

  const TransformExpressionInterface = (function () {
    return function (transform) {
      function _thisFunction(name) {
        switch (name) {
          case 'scale':
          case 'Scale':
          case 'ADBE Scale':
          case 6:
            return _thisFunction.scale
          case 'rotation':
          case 'Rotation':
          case 'ADBE Rotation':
          case 'ADBE Rotate Z':
          case 10:
            return _thisFunction.rotation
          case 'ADBE Rotate X':
            return _thisFunction.xRotation
          case 'ADBE Rotate Y':
            return _thisFunction.yRotation
          case 'position':
          case 'Position':
          case 'ADBE Position':
          case 2:
            return _thisFunction.position
          case 'ADBE Position_0':
            return _thisFunction.xPosition
          case 'ADBE Position_1':
            return _thisFunction.yPosition
          case 'ADBE Position_2':
            return _thisFunction.zPosition
          case 'anchorPoint':
          case 'AnchorPoint':
          case 'Anchor Point':
          case 'ADBE AnchorPoint':
          case 1:
            return _thisFunction.anchorPoint
          case 'opacity':
          case 'Opacity':
          case 11:
            return _thisFunction.opacity
          default:
            return null
        }
      }
      Object.defineProperty(_thisFunction, 'rotation', {
        get: ExpressionPropertyInterface(transform.r || transform.rz),
      })

      Object.defineProperty(_thisFunction, 'zRotation', {
        get: ExpressionPropertyInterface(transform.rz || transform.r),
      })

      Object.defineProperty(_thisFunction, 'xRotation', {
        get: ExpressionPropertyInterface(transform.rx),
      })

      Object.defineProperty(_thisFunction, 'yRotation', {
        get: ExpressionPropertyInterface(transform.ry),
      })
      Object.defineProperty(_thisFunction, 'scale', {
        get: ExpressionPropertyInterface(transform.s),
      })
      var _px
      var _py
      var _pz
      var _transformFactory
      if (transform.p) {
        _transformFactory = ExpressionPropertyInterface(transform.p)
      } else {
        _px = ExpressionPropertyInterface(transform.px)
        _py = ExpressionPropertyInterface(transform.py)
        if (transform.pz) {
          _pz = ExpressionPropertyInterface(transform.pz)
        }
      }
      Object.defineProperty(_thisFunction, 'position', {
        get: function () {
          if (transform.p) {
            return _transformFactory()
          }
          return [_px(), _py(), _pz ? _pz() : 0]
        },
      })

      Object.defineProperty(_thisFunction, 'xPosition', {
        get: ExpressionPropertyInterface(transform.px),
      })

      Object.defineProperty(_thisFunction, 'yPosition', {
        get: ExpressionPropertyInterface(transform.py),
      })

      Object.defineProperty(_thisFunction, 'zPosition', {
        get: ExpressionPropertyInterface(transform.pz),
      })

      Object.defineProperty(_thisFunction, 'anchorPoint', {
        get: ExpressionPropertyInterface(transform.a),
      })

      Object.defineProperty(_thisFunction, 'opacity', {
        get: ExpressionPropertyInterface(transform.o),
      })

      Object.defineProperty(_thisFunction, 'skew', {
        get: ExpressionPropertyInterface(transform.sk),
      })

      Object.defineProperty(_thisFunction, 'skewAxis', {
        get: ExpressionPropertyInterface(transform.sa),
      })

      Object.defineProperty(_thisFunction, 'orientation', {
        get: ExpressionPropertyInterface(transform.or),
      })

      return _thisFunction
    }
  })()

  const LayerExpressionInterface = (function () {
    function getMatrix(time) {
      var toWorldMat = new Matrix()
      if (time !== undefined) {
        var propMatrix = this._elem.finalTransform.mProp.getValueAtTime(time)
        propMatrix.clone(toWorldMat)
      } else {
        var transformMat = this._elem.finalTransform.mProp
        transformMat.applyToMatrix(toWorldMat)
      }
      return toWorldMat
    }

    function toWorldVec(arr, time) {
      var toWorldMat = this.getMatrix(time)
      toWorldMat.props[12] = 0
      toWorldMat.props[13] = 0
      toWorldMat.props[14] = 0
      return this.applyPoint(toWorldMat, arr)
    }

    function toWorld(arr, time) {
      var toWorldMat = this.getMatrix(time)
      return this.applyPoint(toWorldMat, arr)
    }

    function fromWorldVec(arr, time) {
      var toWorldMat = this.getMatrix(time)
      toWorldMat.props[12] = 0
      toWorldMat.props[13] = 0
      toWorldMat.props[14] = 0
      return this.invertPoint(toWorldMat, arr)
    }

    function fromWorld(arr, time) {
      var toWorldMat = this.getMatrix(time)
      return this.invertPoint(toWorldMat, arr)
    }

    function applyPoint(matrix, arr) {
      if (this._elem.hierarchy && this._elem.hierarchy.length) {
        var i
        var len = this._elem.hierarchy.length
        for (i = 0; i < len; i += 1) {
          this._elem.hierarchy[i].finalTransform.mProp.applyToMatrix(matrix)
        }
      }
      return matrix.applyToPointArray(arr[0], arr[1], arr[2] || 0)
    }

    function invertPoint(matrix, arr) {
      if (this._elem.hierarchy && this._elem.hierarchy.length) {
        var i
        var len = this._elem.hierarchy.length
        for (i = 0; i < len; i += 1) {
          this._elem.hierarchy[i].finalTransform.mProp.applyToMatrix(matrix)
        }
      }
      return matrix.inversePoint(arr)
    }

    function fromComp(arr) {
      var toWorldMat = new Matrix()
      toWorldMat.reset()
      this._elem.finalTransform.mProp.applyToMatrix(toWorldMat)
      if (this._elem.hierarchy && this._elem.hierarchy.length) {
        var i
        var len = this._elem.hierarchy.length
        for (i = 0; i < len; i += 1) {
          this._elem.hierarchy[i].finalTransform.mProp.applyToMatrix(toWorldMat)
        }
        return toWorldMat.inversePoint(arr)
      }
      return toWorldMat.inversePoint(arr)
    }

    function sampleImage() {
      return [1, 1, 1, 1]
    }

    return function (elem) {
      var transformInterface

      function _registerMaskInterface(maskManager) {
        _thisLayerFunction.mask = new MaskManagerInterface(maskManager, elem)
      }
      function _registerEffectsInterface(effects) {
        _thisLayerFunction.effect = effects
      }

      function _thisLayerFunction(name) {
        switch (name) {
          case 'ADBE Root Vectors Group':
          case 'Contents':
          case 2:
            return _thisLayerFunction.shapeInterface
          case 1:
          case 6:
          case 'Transform':
          case 'transform':
          case 'ADBE Transform Group':
            return transformInterface
          case 4:
          case 'ADBE Effect Parade':
          case 'effects':
          case 'Effects':
            return _thisLayerFunction.effect
          case 'ADBE Text Properties':
            return _thisLayerFunction.textInterface
          default:
            return null
        }
      }
      _thisLayerFunction.getMatrix = getMatrix
      _thisLayerFunction.invertPoint = invertPoint
      _thisLayerFunction.applyPoint = applyPoint
      _thisLayerFunction.toWorld = toWorld
      _thisLayerFunction.toWorldVec = toWorldVec
      _thisLayerFunction.fromWorld = fromWorld
      _thisLayerFunction.fromWorldVec = fromWorldVec
      _thisLayerFunction.toComp = toWorld
      _thisLayerFunction.fromComp = fromComp
      _thisLayerFunction.sampleImage = sampleImage
      _thisLayerFunction.sourceRectAtTime = elem.sourceRectAtTime.bind(elem)
      _thisLayerFunction._elem = elem
      transformInterface = TransformExpressionInterface(elem.finalTransform.mProp)
      var anchorPointDescriptor = getDescriptor(transformInterface, 'anchorPoint')
      Object.defineProperties(_thisLayerFunction, {
        hasParent: {
          get: function () {
            return elem.hierarchy.length
          },
        },
        parent: {
          get: function () {
            return elem.hierarchy[0].layerInterface
          },
        },
        rotation: getDescriptor(transformInterface, 'rotation'),
        scale: getDescriptor(transformInterface, 'scale'),
        position: getDescriptor(transformInterface, 'position'),
        opacity: getDescriptor(transformInterface, 'opacity'),
        anchorPoint: anchorPointDescriptor,
        anchor_point: anchorPointDescriptor,
        transform: {
          get: function () {
            return transformInterface
          },
        },
        active: {
          get: function () {
            return elem.isInRange
          },
        },
      })

      _thisLayerFunction.startTime = elem.data.st
      _thisLayerFunction.index = elem.data.ind
      _thisLayerFunction.source = elem.data.refId
      _thisLayerFunction.height = elem.data.ty === 0 ? elem.data.h : 100
      _thisLayerFunction.width = elem.data.ty === 0 ? elem.data.w : 100
      _thisLayerFunction.inPoint = elem.data.ip / elem.comp.globalData.frameRate
      _thisLayerFunction.outPoint = elem.data.op / elem.comp.globalData.frameRate
      _thisLayerFunction._name = elem.data.nm

      _thisLayerFunction.registerMaskInterface = _registerMaskInterface
      _thisLayerFunction.registerEffectsInterface = _registerEffectsInterface
      return _thisLayerFunction
    }
  })()

  const propertyGroupFactory = (function () {
    return function (interfaceFunction, parentPropertyGroup) {
      return function (val) {
        val = val === undefined ? 1 : val
        if (val <= 0) {
          return interfaceFunction
        }
        return parentPropertyGroup(val - 1)
      }
    }
  })()

  const PropertyInterface = (function () {
    return function (propertyName, propertyGroup) {
      var interfaceFunction = {
        _name: propertyName,
      }

      function _propertyGroup(val) {
        val = val === undefined ? 1 : val
        if (val <= 0) {
          return interfaceFunction
        }
        return propertyGroup(val - 1)
      }

      return _propertyGroup
    }
  })()

  const EffectsExpressionInterface = (function () {
    var ob = {
      createEffectsInterface: createEffectsInterface,
    }

    function createEffectsInterface(elem, propertyGroup) {
      if (elem.effectsManager) {
        var effectElements = []
        var effectsData = elem.data.ef
        var i
        var len = elem.effectsManager.effectElements.length
        for (i = 0; i < len; i += 1) {
          effectElements.push(
            createGroupInterface(effectsData[i], elem.effectsManager.effectElements[i], propertyGroup, elem),
          )
        }

        var effects = elem.data.ef || []
        var groupInterface = function (name) {
          i = 0
          len = effects.length
          while (i < len) {
            if (name === effects[i].nm || name === effects[i].mn || name === effects[i].ix) {
              return effectElements[i]
            }
            i += 1
          }
          return null
        }
        Object.defineProperty(groupInterface, 'numProperties', {
          get: function () {
            return effects.length
          },
        })
        return groupInterface
      }
      return null
    }

    function createGroupInterface(data, elements, propertyGroup, elem) {
      function groupInterface(name) {
        var effects = data.ef
        var i = 0
        var len = effects.length
        while (i < len) {
          if (name === effects[i].nm || name === effects[i].mn || name === effects[i].ix) {
            if (effects[i].ty === 5) {
              return effectElements[i]
            }
            return effectElements[i]()
          }
          i += 1
        }
        throw new Error()
      }
      var _propertyGroup = propertyGroupFactory(groupInterface, propertyGroup)

      var effectElements = []
      var i
      var len = data.ef.length
      for (i = 0; i < len; i += 1) {
        if (data.ef[i].ty === 5) {
          effectElements.push(
            createGroupInterface(
              data.ef[i],
              elements.effectElements[i],
              elements.effectElements[i].propertyGroup,
              elem,
            ),
          )
        } else {
          effectElements.push(createValueInterface(elements.effectElements[i], data.ef[i].ty, elem, _propertyGroup))
        }
      }

      if (data.mn === 'ADBE Color Control') {
        Object.defineProperty(groupInterface, 'color', {
          get: function () {
            return effectElements[0]()
          },
        })
      }
      Object.defineProperties(groupInterface, {
        numProperties: {
          get: function () {
            return data.np
          },
        },
        _name: { value: data.nm },
        propertyGroup: { value: _propertyGroup },
      })
      groupInterface.enabled = data.en !== 0
      groupInterface.active = groupInterface.enabled
      return groupInterface
    }

    function createValueInterface(element, type, elem, propertyGroup) {
      var expressionProperty = ExpressionPropertyInterface(element.p)
      function interfaceFunction() {
        if (type === 10) {
          return elem.comp.compInterface(element.p.v)
        }
        return expressionProperty()
      }

      if (element.p.setGroupProperty) {
        element.p.setGroupProperty(PropertyInterface('', propertyGroup))
      }

      return interfaceFunction
    }

    return ob
  })()

  const CompExpressionInterface = (function () {
    return function (comp) {
      function _thisLayerFunction(name) {
        var i = 0
        var len = comp.layers.length
        while (i < len) {
          if (comp.layers[i].nm === name || comp.layers[i].ind === name) {
            return comp.elements[i].layerInterface
          }
          i += 1
        }
        return null
        // return {active:false};
      }
      Object.defineProperty(_thisLayerFunction, '_name', { value: comp.data.nm })
      _thisLayerFunction.layer = _thisLayerFunction
      _thisLayerFunction.pixelAspect = 1
      _thisLayerFunction.height = comp.data.h || comp.globalData.compSize.h
      _thisLayerFunction.width = comp.data.w || comp.globalData.compSize.w
      _thisLayerFunction.pixelAspect = 1
      _thisLayerFunction.frameDuration = 1 / comp.globalData.frameRate
      _thisLayerFunction.displayStartTime = 0
      _thisLayerFunction.numLayers = comp.layers.length
      return _thisLayerFunction
    }
  })()

  const ShapePathInterface = (function () {
    return function pathInterfaceFactory(shape, view, propertyGroup) {
      var prop = view.sh

      function interfaceFunction(val) {
        if (
          val === 'Shape' ||
          val === 'shape' ||
          val === 'Path' ||
          val === 'path' ||
          val === 'ADBE Vector Shape' ||
          val === 2
        ) {
          return interfaceFunction.path
        }
        return null
      }

      var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup)
      prop.setGroupProperty(PropertyInterface('Path', _propertyGroup))
      Object.defineProperties(interfaceFunction, {
        path: {
          get: function () {
            if (prop.k) {
              prop.getValue()
            }
            return prop
          },
        },
        shape: {
          get: function () {
            if (prop.k) {
              prop.getValue()
            }
            return prop
          },
        },
        _name: { value: shape.nm },
        ix: { value: shape.ix },
        propertyIndex: { value: shape.ix },
        mn: { value: shape.mn },
        propertyGroup: { value: propertyGroup },
      })
      return interfaceFunction
    }
  })()

  const ShapeExpressionInterface = (function () {
    function iterateElements(shapes, view, propertyGroup) {
      var arr = []
      var i
      var len = shapes ? shapes.length : 0
      for (i = 0; i < len; i += 1) {
        if (shapes[i].ty === 'gr') {
          arr.push(groupInterfaceFactory(shapes[i], view[i], propertyGroup))
        } else if (shapes[i].ty === 'fl') {
          arr.push(fillInterfaceFactory(shapes[i], view[i], propertyGroup))
        } else if (shapes[i].ty === 'st') {
          arr.push(strokeInterfaceFactory(shapes[i], view[i], propertyGroup))
        } else if (shapes[i].ty === 'tm') {
          arr.push(trimInterfaceFactory(shapes[i], view[i], propertyGroup))
        } else if (shapes[i].ty === 'tr') {
          // arr.push(transformInterfaceFactory(shapes[i],view[i],propertyGroup));
        } else if (shapes[i].ty === 'el') {
          arr.push(ellipseInterfaceFactory(shapes[i], view[i], propertyGroup))
        } else if (shapes[i].ty === 'sr') {
          arr.push(starInterfaceFactory(shapes[i], view[i], propertyGroup))
        } else if (shapes[i].ty === 'sh') {
          arr.push(ShapePathInterface(shapes[i], view[i], propertyGroup))
        } else if (shapes[i].ty === 'rc') {
          arr.push(rectInterfaceFactory(shapes[i], view[i], propertyGroup))
        } else if (shapes[i].ty === 'rd') {
          arr.push(roundedInterfaceFactory(shapes[i], view[i], propertyGroup))
        } else if (shapes[i].ty === 'rp') {
          arr.push(repeaterInterfaceFactory(shapes[i], view[i], propertyGroup))
        } else if (shapes[i].ty === 'gf') {
          arr.push(gradientFillInterfaceFactory(shapes[i], view[i], propertyGroup))
        } else {
          arr.push(defaultInterfaceFactory(shapes[i], view[i], propertyGroup))
        }
      }
      return arr
    }

    function contentsInterfaceFactory(shape, view, propertyGroup) {
      var interfaces
      var interfaceFunction = function _interfaceFunction(value) {
        var i = 0
        var len = interfaces.length
        while (i < len) {
          if (
            interfaces[i]._name === value ||
            interfaces[i].mn === value ||
            interfaces[i].propertyIndex === value ||
            interfaces[i].ix === value ||
            interfaces[i].ind === value
          ) {
            return interfaces[i]
          }
          i += 1
        }
        if (typeof value === 'number') {
          return interfaces[value - 1]
        }
        return null
      }

      interfaceFunction.propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup)
      interfaces = iterateElements(shape.it, view.it, interfaceFunction.propertyGroup)
      interfaceFunction.numProperties = interfaces.length
      var transformInterface = transformInterfaceFactory(
        shape.it[shape.it.length - 1],
        view.it[view.it.length - 1],
        interfaceFunction.propertyGroup,
      )
      interfaceFunction.transform = transformInterface
      interfaceFunction.propertyIndex = shape.cix
      interfaceFunction._name = shape.nm

      return interfaceFunction
    }

    function groupInterfaceFactory(shape, view, propertyGroup) {
      var interfaceFunction = function _interfaceFunction(value) {
        switch (value) {
          case 'ADBE Vectors Group':
          case 'Contents':
          case 2:
            return interfaceFunction.content
          // Not necessary for now. Keeping them here in case a new case appears
          // case 'ADBE Vector Transform Group':
          // case 3:
          default:
            return interfaceFunction.transform
        }
      }
      interfaceFunction.propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup)
      var content = contentsInterfaceFactory(shape, view, interfaceFunction.propertyGroup)
      var transformInterface = transformInterfaceFactory(
        shape.it[shape.it.length - 1],
        view.it[view.it.length - 1],
        interfaceFunction.propertyGroup,
      )
      interfaceFunction.content = content
      interfaceFunction.transform = transformInterface
      Object.defineProperty(interfaceFunction, '_name', {
        get: function () {
          return shape.nm
        },
      })
      // interfaceFunction.content = interfaceFunction;
      interfaceFunction.numProperties = shape.np
      interfaceFunction.propertyIndex = shape.ix
      interfaceFunction.nm = shape.nm
      interfaceFunction.mn = shape.mn
      return interfaceFunction
    }

    function fillInterfaceFactory(shape, view, propertyGroup) {
      function interfaceFunction(val) {
        if (val === 'Color' || val === 'color') {
          return interfaceFunction.color
        }
        if (val === 'Opacity' || val === 'opacity') {
          return interfaceFunction.opacity
        }
        return null
      }
      Object.defineProperties(interfaceFunction, {
        color: {
          get: ExpressionPropertyInterface(view.c),
        },
        opacity: {
          get: ExpressionPropertyInterface(view.o),
        },
        _name: { value: shape.nm },
        mn: { value: shape.mn },
      })

      view.c.setGroupProperty(PropertyInterface('Color', propertyGroup))
      view.o.setGroupProperty(PropertyInterface('Opacity', propertyGroup))
      return interfaceFunction
    }

    function gradientFillInterfaceFactory(shape, view, propertyGroup) {
      function interfaceFunction(val) {
        if (val === 'Start Point' || val === 'start point') {
          return interfaceFunction.startPoint
        }
        if (val === 'End Point' || val === 'end point') {
          return interfaceFunction.endPoint
        }
        if (val === 'Opacity' || val === 'opacity') {
          return interfaceFunction.opacity
        }
        return null
      }
      Object.defineProperties(interfaceFunction, {
        startPoint: {
          get: ExpressionPropertyInterface(view.s),
        },
        endPoint: {
          get: ExpressionPropertyInterface(view.e),
        },
        opacity: {
          get: ExpressionPropertyInterface(view.o),
        },
        type: {
          get: function () {
            return 'a'
          },
        },
        _name: { value: shape.nm },
        mn: { value: shape.mn },
      })

      view.s.setGroupProperty(PropertyInterface('Start Point', propertyGroup))
      view.e.setGroupProperty(PropertyInterface('End Point', propertyGroup))
      view.o.setGroupProperty(PropertyInterface('Opacity', propertyGroup))
      return interfaceFunction
    }
    function defaultInterfaceFactory() {
      function interfaceFunction() {
        return null
      }
      return interfaceFunction
    }

    function strokeInterfaceFactory(shape, view, propertyGroup) {
      var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup)
      var _dashPropertyGroup = propertyGroupFactory(dashOb, _propertyGroup)
      function addPropertyToDashOb(i) {
        Object.defineProperty(dashOb, shape.d[i].nm, {
          get: ExpressionPropertyInterface(view.d.dataProps[i].p),
        })
      }
      var i
      var len = shape.d ? shape.d.length : 0
      var dashOb = {}
      for (i = 0; i < len; i += 1) {
        addPropertyToDashOb(i)
        view.d.dataProps[i].p.setGroupProperty(_dashPropertyGroup)
      }

      function interfaceFunction(val) {
        if (val === 'Color' || val === 'color') {
          return interfaceFunction.color
        }
        if (val === 'Opacity' || val === 'opacity') {
          return interfaceFunction.opacity
        }
        if (val === 'Stroke Width' || val === 'stroke width') {
          return interfaceFunction.strokeWidth
        }
        return null
      }
      Object.defineProperties(interfaceFunction, {
        color: {
          get: ExpressionPropertyInterface(view.c),
        },
        opacity: {
          get: ExpressionPropertyInterface(view.o),
        },
        strokeWidth: {
          get: ExpressionPropertyInterface(view.w),
        },
        dash: {
          get: function () {
            return dashOb
          },
        },
        _name: { value: shape.nm },
        mn: { value: shape.mn },
      })

      view.c.setGroupProperty(PropertyInterface('Color', _propertyGroup))
      view.o.setGroupProperty(PropertyInterface('Opacity', _propertyGroup))
      view.w.setGroupProperty(PropertyInterface('Stroke Width', _propertyGroup))
      return interfaceFunction
    }

    function trimInterfaceFactory(shape, view, propertyGroup) {
      function interfaceFunction(val) {
        if (val === shape.e.ix || val === 'End' || val === 'end') {
          return interfaceFunction.end
        }
        if (val === shape.s.ix) {
          return interfaceFunction.start
        }
        if (val === shape.o.ix) {
          return interfaceFunction.offset
        }
        return null
      }

      var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup)
      interfaceFunction.propertyIndex = shape.ix

      view.s.setGroupProperty(PropertyInterface('Start', _propertyGroup))
      view.e.setGroupProperty(PropertyInterface('End', _propertyGroup))
      view.o.setGroupProperty(PropertyInterface('Offset', _propertyGroup))
      interfaceFunction.propertyIndex = shape.ix
      interfaceFunction.propertyGroup = propertyGroup

      Object.defineProperties(interfaceFunction, {
        start: {
          get: ExpressionPropertyInterface(view.s),
        },
        end: {
          get: ExpressionPropertyInterface(view.e),
        },
        offset: {
          get: ExpressionPropertyInterface(view.o),
        },
        _name: { value: shape.nm },
      })
      interfaceFunction.mn = shape.mn
      return interfaceFunction
    }

    function transformInterfaceFactory(shape, view, propertyGroup) {
      function interfaceFunction(value) {
        if (shape.a.ix === value || value === 'Anchor Point') {
          return interfaceFunction.anchorPoint
        }
        if (shape.o.ix === value || value === 'Opacity') {
          return interfaceFunction.opacity
        }
        if (shape.p.ix === value || value === 'Position') {
          return interfaceFunction.position
        }
        if (shape.r.ix === value || value === 'Rotation' || value === 'ADBE Vector Rotation') {
          return interfaceFunction.rotation
        }
        if (shape.s.ix === value || value === 'Scale') {
          return interfaceFunction.scale
        }
        if ((shape.sk && shape.sk.ix === value) || value === 'Skew') {
          return interfaceFunction.skew
        }
        if ((shape.sa && shape.sa.ix === value) || value === 'Skew Axis') {
          return interfaceFunction.skewAxis
        }
        return null
      }
      var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup)
      view.transform.mProps.o.setGroupProperty(PropertyInterface('Opacity', _propertyGroup))
      view.transform.mProps.p.setGroupProperty(PropertyInterface('Position', _propertyGroup))
      view.transform.mProps.a.setGroupProperty(PropertyInterface('Anchor Point', _propertyGroup))
      view.transform.mProps.s.setGroupProperty(PropertyInterface('Scale', _propertyGroup))
      view.transform.mProps.r.setGroupProperty(PropertyInterface('Rotation', _propertyGroup))
      if (view.transform.mProps.sk) {
        view.transform.mProps.sk.setGroupProperty(PropertyInterface('Skew', _propertyGroup))
        view.transform.mProps.sa.setGroupProperty(PropertyInterface('Skew Angle', _propertyGroup))
      }
      view.transform.op.setGroupProperty(PropertyInterface('Opacity', _propertyGroup))
      Object.defineProperties(interfaceFunction, {
        opacity: {
          get: ExpressionPropertyInterface(view.transform.mProps.o),
        },
        position: {
          get: ExpressionPropertyInterface(view.transform.mProps.p),
        },
        anchorPoint: {
          get: ExpressionPropertyInterface(view.transform.mProps.a),
        },
        scale: {
          get: ExpressionPropertyInterface(view.transform.mProps.s),
        },
        rotation: {
          get: ExpressionPropertyInterface(view.transform.mProps.r),
        },
        skew: {
          get: ExpressionPropertyInterface(view.transform.mProps.sk),
        },
        skewAxis: {
          get: ExpressionPropertyInterface(view.transform.mProps.sa),
        },
        _name: { value: shape.nm },
      })
      interfaceFunction.ty = 'tr'
      interfaceFunction.mn = shape.mn
      interfaceFunction.propertyGroup = propertyGroup
      return interfaceFunction
    }

    function ellipseInterfaceFactory(shape, view, propertyGroup) {
      function interfaceFunction(value) {
        if (shape.p.ix === value) {
          return interfaceFunction.position
        }
        if (shape.s.ix === value) {
          return interfaceFunction.size
        }
        return null
      }
      var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup)
      interfaceFunction.propertyIndex = shape.ix
      var prop = view.sh.ty === 'tm' ? view.sh.prop : view.sh
      prop.s.setGroupProperty(PropertyInterface('Size', _propertyGroup))
      prop.p.setGroupProperty(PropertyInterface('Position', _propertyGroup))

      Object.defineProperties(interfaceFunction, {
        size: {
          get: ExpressionPropertyInterface(prop.s),
        },
        position: {
          get: ExpressionPropertyInterface(prop.p),
        },
        _name: { value: shape.nm },
      })
      interfaceFunction.mn = shape.mn
      return interfaceFunction
    }

    function starInterfaceFactory(shape, view, propertyGroup) {
      function interfaceFunction(value) {
        if (shape.p.ix === value) {
          return interfaceFunction.position
        }
        if (shape.r.ix === value) {
          return interfaceFunction.rotation
        }
        if (shape.pt.ix === value) {
          return interfaceFunction.points
        }
        if (shape.or.ix === value || value === 'ADBE Vector Star Outer Radius') {
          return interfaceFunction.outerRadius
        }
        if (shape.os.ix === value) {
          return interfaceFunction.outerRoundness
        }
        if (shape.ir && (shape.ir.ix === value || value === 'ADBE Vector Star Inner Radius')) {
          return interfaceFunction.innerRadius
        }
        if (shape.is && shape.is.ix === value) {
          return interfaceFunction.innerRoundness
        }
        return null
      }

      var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup)
      var prop = view.sh.ty === 'tm' ? view.sh.prop : view.sh
      interfaceFunction.propertyIndex = shape.ix
      prop.or.setGroupProperty(PropertyInterface('Outer Radius', _propertyGroup))
      prop.os.setGroupProperty(PropertyInterface('Outer Roundness', _propertyGroup))
      prop.pt.setGroupProperty(PropertyInterface('Points', _propertyGroup))
      prop.p.setGroupProperty(PropertyInterface('Position', _propertyGroup))
      prop.r.setGroupProperty(PropertyInterface('Rotation', _propertyGroup))
      if (shape.ir) {
        prop.ir.setGroupProperty(PropertyInterface('Inner Radius', _propertyGroup))
        prop.is.setGroupProperty(PropertyInterface('Inner Roundness', _propertyGroup))
      }

      Object.defineProperties(interfaceFunction, {
        position: {
          get: ExpressionPropertyInterface(prop.p),
        },
        rotation: {
          get: ExpressionPropertyInterface(prop.r),
        },
        points: {
          get: ExpressionPropertyInterface(prop.pt),
        },
        outerRadius: {
          get: ExpressionPropertyInterface(prop.or),
        },
        outerRoundness: {
          get: ExpressionPropertyInterface(prop.os),
        },
        innerRadius: {
          get: ExpressionPropertyInterface(prop.ir),
        },
        innerRoundness: {
          get: ExpressionPropertyInterface(prop.is),
        },
        _name: { value: shape.nm },
      })
      interfaceFunction.mn = shape.mn
      return interfaceFunction
    }

    function rectInterfaceFactory(shape, view, propertyGroup) {
      function interfaceFunction(value) {
        if (shape.p.ix === value) {
          return interfaceFunction.position
        }
        if (shape.r.ix === value) {
          return interfaceFunction.roundness
        }
        if (shape.s.ix === value || value === 'Size' || value === 'ADBE Vector Rect Size') {
          return interfaceFunction.size
        }
        return null
      }
      var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup)

      var prop = view.sh.ty === 'tm' ? view.sh.prop : view.sh
      interfaceFunction.propertyIndex = shape.ix
      prop.p.setGroupProperty(PropertyInterface('Position', _propertyGroup))
      prop.s.setGroupProperty(PropertyInterface('Size', _propertyGroup))
      prop.r.setGroupProperty(PropertyInterface('Rotation', _propertyGroup))

      Object.defineProperties(interfaceFunction, {
        position: {
          get: ExpressionPropertyInterface(prop.p),
        },
        roundness: {
          get: ExpressionPropertyInterface(prop.r),
        },
        size: {
          get: ExpressionPropertyInterface(prop.s),
        },
        _name: { value: shape.nm },
      })
      interfaceFunction.mn = shape.mn
      return interfaceFunction
    }

    function roundedInterfaceFactory(shape, view, propertyGroup) {
      function interfaceFunction(value) {
        if (shape.r.ix === value || value === 'Round Corners 1') {
          return interfaceFunction.radius
        }
        return null
      }

      var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup)
      var prop = view
      interfaceFunction.propertyIndex = shape.ix
      prop.rd.setGroupProperty(PropertyInterface('Radius', _propertyGroup))

      Object.defineProperties(interfaceFunction, {
        radius: {
          get: ExpressionPropertyInterface(prop.rd),
        },
        _name: { value: shape.nm },
      })
      interfaceFunction.mn = shape.mn
      return interfaceFunction
    }

    function repeaterInterfaceFactory(shape, view, propertyGroup) {
      function interfaceFunction(value) {
        if (shape.c.ix === value || value === 'Copies') {
          return interfaceFunction.copies
        }
        if (shape.o.ix === value || value === 'Offset') {
          return interfaceFunction.offset
        }
        return null
      }

      var _propertyGroup = propertyGroupFactory(interfaceFunction, propertyGroup)
      var prop = view
      interfaceFunction.propertyIndex = shape.ix
      prop.c.setGroupProperty(PropertyInterface('Copies', _propertyGroup))
      prop.o.setGroupProperty(PropertyInterface('Offset', _propertyGroup))
      Object.defineProperties(interfaceFunction, {
        copies: {
          get: ExpressionPropertyInterface(prop.c),
        },
        offset: {
          get: ExpressionPropertyInterface(prop.o),
        },
        _name: { value: shape.nm },
      })
      interfaceFunction.mn = shape.mn
      return interfaceFunction
    }

    return function (shapes, view, propertyGroup) {
      var interfaces
      function _interfaceFunction(value) {
        if (typeof value === 'number') {
          value = value === undefined ? 1 : value
          if (value === 0) {
            return propertyGroup
          }
          return interfaces[value - 1]
        }
        var i = 0
        var len = interfaces.length
        while (i < len) {
          if (interfaces[i]._name === value) {
            return interfaces[i]
          }
          i += 1
        }
        return null
      }
      function parentGroupWrapper() {
        return propertyGroup
      }
      _interfaceFunction.propertyGroup = propertyGroupFactory(_interfaceFunction, parentGroupWrapper)
      interfaces = iterateElements(shapes, view, _interfaceFunction.propertyGroup)
      _interfaceFunction.numProperties = interfaces.length
      _interfaceFunction._name = 'Contents'
      return _interfaceFunction
    }
  })()

  const TextExpressionInterface = (function () {
    return function (elem) {
      var _prevValue
      var _sourceText
      function _thisLayerFunction(name) {
        switch (name) {
          case 'ADBE Text Document':
            return _thisLayerFunction.sourceText
          default:
            return null
        }
      }
      Object.defineProperty(_thisLayerFunction, 'sourceText', {
        get: function () {
          elem.textProperty.getValue()
          var stringValue = elem.textProperty.currentData.t
          if (stringValue !== _prevValue) {
            elem.textProperty.currentData.t = _prevValue
            _sourceText = new String(stringValue) // eslint-disable-line no-new-wrappers
            _sourceText.value = stringValue || new String(stringValue) // eslint-disable-line no-new-wrappers
          }
          return _sourceText
        },
      })
      return _thisLayerFunction
    }
  })()

  const getBlendMode = (function () {
    var blendModeEnums = {
      0: 'source-over',
      1: 'multiply',
      2: 'screen',
      3: 'overlay',
      4: 'darken',
      5: 'lighten',
      6: 'color-dodge',
      7: 'color-burn',
      8: 'hard-light',
      9: 'soft-light',
      10: 'difference',
      11: 'exclusion',
      12: 'hue',
      13: 'saturation',
      14: 'color',
      15: 'luminosity',
    }

    return function (mode) {
      return blendModeEnums[mode] || ''
    }
  })()

  function SliderEffect(data, elem, container) {
    this.p = PropertyFactory.getProp(elem, data.v, 0, 0, container)
  }
  function AngleEffect(data, elem, container) {
    this.p = PropertyFactory.getProp(elem, data.v, 0, 0, container)
  }
  function ColorEffect(data, elem, container) {
    this.p = PropertyFactory.getProp(elem, data.v, 1, 0, container)
  }
  function PointEffect(data, elem, container) {
    this.p = PropertyFactory.getProp(elem, data.v, 1, 0, container)
  }
  function LayerIndexEffect(data, elem, container) {
    this.p = PropertyFactory.getProp(elem, data.v, 0, 0, container)
  }
  function MaskIndexEffect(data, elem, container) {
    this.p = PropertyFactory.getProp(elem, data.v, 0, 0, container)
  }
  function CheckboxEffect(data, elem, container) {
    this.p = PropertyFactory.getProp(elem, data.v, 0, 0, container)
  }
  function NoValueEffect() {
    this.p = {}
  }

  function EffectsManager(data, element) {
    var effects = data.ef || []
    this.effectElements = []
    var i
    var len = effects.length
    var effectItem
    for (i = 0; i < len; i += 1) {
      effectItem = new GroupEffect(effects[i], element)
      this.effectElements.push(effectItem)
    }
  }

  function GroupEffect(data, element) {
    this.init(data, element)
  }

  extendPrototype([DynamicPropertyContainer], GroupEffect)

  GroupEffect.prototype.getValue = GroupEffect.prototype.iterateDynamicProperties

  GroupEffect.prototype.init = function (data, element) {
    this.data = data
    this.effectElements = []
    this.initDynamicPropertyContainer(element)
    var i
    var len = this.data.ef.length
    var eff
    var effects = this.data.ef
    for (i = 0; i < len; i += 1) {
      eff = null
      switch (effects[i].ty) {
        case 0:
          eff = new SliderEffect(effects[i], element, this)
          break
        case 1:
          eff = new AngleEffect(effects[i], element, this)
          break
        case 2:
          eff = new ColorEffect(effects[i], element, this)
          break
        case 3:
          eff = new PointEffect(effects[i], element, this)
          break
        case 4:
        case 7:
          eff = new CheckboxEffect(effects[i], element, this)
          break
        case 10:
          eff = new LayerIndexEffect(effects[i], element, this)
          break
        case 11:
          eff = new MaskIndexEffect(effects[i], element, this)
          break
        case 5:
          eff = new EffectsManager(effects[i], element, this)
          break
        // case 6:
        default:
          eff = new NoValueEffect(effects[i], element, this)
          break
      }
      if (eff) {
        this.effectElements.push(eff)
      }
    }
  }

  function BaseElement() {}

  BaseElement.prototype = {
    checkMasks: function () {
      if (!this.data.hasMask) {
        return false
      }
      var i = 0
      var len = this.data.masksProperties.length
      while (i < len) {
        if (this.data.masksProperties[i].mode !== 'n' && this.data.masksProperties[i].cl !== false) {
          return true
        }
        i += 1
      }
      return false
    },
    initExpressions: function () {
      this.layerInterface = LayerExpressionInterface(this)
      if (this.data.hasMask && this.maskManager) {
        this.layerInterface.registerMaskInterface(this.maskManager)
      }
      var effectsInterface = EffectsExpressionInterface.createEffectsInterface(this, this.layerInterface)
      this.layerInterface.registerEffectsInterface(effectsInterface)

      if (this.data.ty === 0 || this.data.xt) {
        this.compInterface = CompExpressionInterface(this)
      } else if (this.data.ty === 4) {
        this.layerInterface.shapeInterface = ShapeExpressionInterface(
          this.shapesData,
          this.itemsData,
          this.layerInterface,
        )
        this.layerInterface.content = this.layerInterface.shapeInterface
      } else if (this.data.ty === 5) {
        this.layerInterface.textInterface = TextExpressionInterface(this)
        this.layerInterface.text = this.layerInterface.textInterface
      }
    },
    setBlendMode: function () {
      var blendModeValue = getBlendMode(this.data.bm)
      var elem = this.baseElement || this.layerElement

      elem.style['mix-blend-mode'] = blendModeValue
    },
    initBaseData: function (data, globalData, comp) {
      this.globalData = globalData
      this.comp = comp
      this.data = data
      this.layerId = createElementID()

      // Stretch factor for old animations missing this property.
      if (!this.data.sr) {
        this.data.sr = 1
      }
      // effects manager
      this.effectsManager = new EffectsManager(this.data, this, this.dynamicProperties)
    },
    getType: function () {
      return this.type
    },
    sourceRectAtTime: function () {},
  }

  /**
   * @file
   * Handles element's layer frame update.
   * Checks layer in point and out point
   *
   */

  function FrameElement() {}

  FrameElement.prototype = {
    /**
     * @function
     * Initializes frame related properties.
     *
     */
    initFrame: function () {
      // set to true when inpoint is rendered
      this._isFirstFrame = false
      // list of animated properties
      this.dynamicProperties = []
      // If layer has been modified in current tick this will be true
      this._mdf = false
    },
    /**
     * @function
     * Calculates all dynamic values
     *
     * @param {number} num
     * current frame number in Layer's time
     * @param {boolean} isVisible
     * if layers is currently in range
     *
     */
    prepareProperties: function (num, isVisible) {
      var i
      var len = this.dynamicProperties.length
      for (i = 0; i < len; i += 1) {
        if (isVisible || (this._isParent && this.dynamicProperties[i].propType === 'transform')) {
          this.dynamicProperties[i].getValue()
          if (this.dynamicProperties[i]._mdf) {
            this.globalData._mdf = true
            this._mdf = true
          }
        }
      }
    },
    addDynamicProperty: function (prop) {
      if (this.dynamicProperties.indexOf(prop) === -1) {
        this.dynamicProperties.push(prop)
      }
    },
  }

  const FootageInterface = (function () {
    var outlineInterfaceFactory = function (elem) {
      var currentPropertyName = ''
      var currentProperty = elem.getFootageData()
      function init() {
        currentPropertyName = ''
        currentProperty = elem.getFootageData()
        return searchProperty
      }
      function searchProperty(value) {
        if (currentProperty[value]) {
          currentPropertyName = value
          currentProperty = currentProperty[value]
          if (typeof currentProperty === 'object') {
            return searchProperty
          }
          return currentProperty
        }
        var propertyNameIndex = value.indexOf(currentPropertyName)
        if (propertyNameIndex !== -1) {
          var index = parseInt(value.substr(propertyNameIndex + currentPropertyName.length), 10)
          currentProperty = currentProperty[index]
          if (typeof currentProperty === 'object') {
            return searchProperty
          }
          return currentProperty
        }
        return ''
      }
      return init
    }

    var dataInterfaceFactory = function (elem) {
      function interfaceFunction(value) {
        if (value === 'Outline') {
          return interfaceFunction.outlineInterface()
        }
        return null
      }

      interfaceFunction._name = 'Outline'
      interfaceFunction.outlineInterface = outlineInterfaceFactory(elem)
      return interfaceFunction
    }

    return function (elem) {
      function _interfaceFunction(value) {
        if (value === 'Data') {
          return _interfaceFunction.dataInterface
        }
        return null
      }

      _interfaceFunction._name = 'Data'
      _interfaceFunction.dataInterface = dataInterfaceFactory(elem)
      return _interfaceFunction
    }
  })()

  function FootageElement(data, globalData, comp) {
    this.initFrame()
    this.initRenderable()
    this.assetData = globalData.getAssetData(data.refId)
    this.footageData = globalData.imageLoader.getAsset(this.assetData)
    this.initBaseData(data, globalData, comp)
  }

  FootageElement.prototype.prepareFrame = function () {}

  extendPrototype([RenderableElement, BaseElement, FrameElement], FootageElement)

  FootageElement.prototype.getBaseElement = function () {
    return null
  }

  FootageElement.prototype.renderFrame = function () {}

  FootageElement.prototype.destroy = function () {}

  FootageElement.prototype.initExpressions = function () {
    this.layerInterface = FootageInterface(this)
  }

  FootageElement.prototype.getFootageData = function () {
    return this.footageData
  }

  function AudioElement(data, globalData, comp) {
    this.initFrame()
    this.initRenderable()
    this.assetData = globalData.getAssetData(data.refId)
    this.initBaseData(data, globalData, comp)
    this._isPlaying = false
    this._canPlay = false
    var assetPath = this.globalData.getAssetsPath(this.assetData)
    this.audio = this.globalData.audioController.createAudio(assetPath)
    this._currentTime = 0
    this.globalData.audioController.addAudio(this)
    this._volumeMultiplier = 1
    this._volume = 1
    this._previousVolume = null
    this.tm = data.tm ? PropertyFactory.getProp(this, data.tm, 0, globalData.frameRate, this) : { _placeholder: true }
    this.lv = PropertyFactory.getProp(this, data.au && data.au.lv ? data.au.lv : { k: [100] }, 1, 0.01, this)
  }

  AudioElement.prototype.prepareFrame = function (num) {
    this.prepareRenderableFrame(num, true)
    this.prepareProperties(num, true)
    if (!this.tm._placeholder) {
      var timeRemapped = this.tm.v
      this._currentTime = timeRemapped
    } else {
      this._currentTime = num / this.data.sr
    }
    this._volume = this.lv.v[0]
    var totalVolume = this._volume * this._volumeMultiplier
    if (this._previousVolume !== totalVolume) {
      this._previousVolume = totalVolume
      this.audio.volume(totalVolume)
    }
  }

  extendPrototype([RenderableElement, BaseElement, FrameElement], AudioElement)

  AudioElement.prototype.renderFrame = function () {
    if (this.isInRange && this._canPlay) {
      if (!this._isPlaying) {
        this.audio.play()
        this.audio.seek(this._currentTime / this.globalData.frameRate)
        this._isPlaying = true
      } else if (
        !this.audio.playing() ||
        Math.abs(this._currentTime / this.globalData.frameRate - this.audio.seek()) > 0.1
      ) {
        this.audio.seek(this._currentTime / this.globalData.frameRate)
      }
    }
  }

  AudioElement.prototype.show = function () {
    // this.audio.play()
  }

  AudioElement.prototype.hide = function () {
    this.audio.pause()
    this._isPlaying = false
  }

  AudioElement.prototype.pause = function () {
    this.audio.pause()
    this._isPlaying = false
    this._canPlay = false
  }

  AudioElement.prototype.resume = function () {
    this._canPlay = true
  }

  AudioElement.prototype.setRate = function (rateValue) {
    this.audio.rate(rateValue)
  }

  AudioElement.prototype.volume = function (volumeValue) {
    this._volumeMultiplier = volumeValue
    this._previousVolume = volumeValue * this._volume
    this.audio.volume(this._previousVolume)
  }

  AudioElement.prototype.getBaseElement = function () {
    return null
  }

  AudioElement.prototype.destroy = function () {}

  AudioElement.prototype.sourceRectAtTime = function () {}

  AudioElement.prototype.initExpressions = function () {}

  function BaseRenderer() {}
  BaseRenderer.prototype.checkLayers = function (num) {
    var i
    var len = this.layers.length
    var data
    this.completeLayers = true
    for (i = len - 1; i >= 0; i -= 1) {
      if (!this.elements[i]) {
        data = this.layers[i]
        if (data.ip - data.st <= num - this.layers[i].st && data.op - data.st > num - this.layers[i].st) {
          this.buildItem(i)
        }
      }
      this.completeLayers = this.elements[i] ? this.completeLayers : false
    }
    this.checkPendingElements()
  }

  BaseRenderer.prototype.createItem = function (layer) {
    switch (layer.ty) {
      case 2:
        return this.createImage(layer)
      case 0:
        return this.createComp(layer)
      case 1:
        return this.createSolid(layer)
      case 3:
        return this.createNull(layer)
      case 4:
        return this.createShape(layer)
      case 5:
        return this.createText(layer)
      case 6:
        return this.createAudio(layer)
      case 13:
        return this.createCamera(layer)
      case 15:
        return this.createFootage(layer)
      default:
        return this.createNull(layer)
    }
  }

  BaseRenderer.prototype.createCamera = function () {
    throw new Error("You're using a 3d camera. Try the html renderer.")
  }

  BaseRenderer.prototype.createAudio = function (data) {
    return new AudioElement(data, this.globalData, this)
  }

  BaseRenderer.prototype.createFootage = function (data) {
    return new FootageElement(data, this.globalData, this)
  }

  BaseRenderer.prototype.buildAllItems = function () {
    var i
    var len = this.layers.length
    for (i = 0; i < len; i += 1) {
      this.buildItem(i)
    }
    this.checkPendingElements()
  }

  BaseRenderer.prototype.includeLayers = function (newLayers) {
    this.completeLayers = false
    var i
    var len = newLayers.length
    var j
    var jLen = this.layers.length
    for (i = 0; i < len; i += 1) {
      j = 0
      while (j < jLen) {
        if (this.layers[j].id === newLayers[i].id) {
          this.layers[j] = newLayers[i]
          break
        }
        j += 1
      }
    }
  }

  BaseRenderer.prototype.setProjectInterface = function (pInterface) {
    this.globalData.projectInterface = pInterface
  }

  BaseRenderer.prototype.initItems = function () {
    if (!this.globalData.progressiveLoad) {
      this.buildAllItems()
    }
  }
  BaseRenderer.prototype.buildElementParenting = function (element, parentName, hierarchy) {
    var elements = this.elements
    var layers = this.layers
    var i = 0
    var len = layers.length
    while (i < len) {
      if (layers[i].ind == parentName) {
        // eslint-disable-line eqeqeq
        if (!elements[i] || elements[i] === true) {
          this.buildItem(i)
          this.addPendingElement(element)
        } else {
          hierarchy.push(elements[i])
          elements[i].setAsParent()
          if (layers[i].parent !== undefined) {
            this.buildElementParenting(element, layers[i].parent, hierarchy)
          } else {
            element.setHierarchy(hierarchy)
          }
        }
      }
      i += 1
    }
  }

  BaseRenderer.prototype.addPendingElement = function (element) {
    this.pendingElements.push(element)
  }

  BaseRenderer.prototype.searchExtraCompositions = function (assets) {
    var i
    var len = assets.length
    for (i = 0; i < len; i += 1) {
      if (assets[i].xt) {
        var comp = this.createComp(assets[i])
        comp.initExpressions()
        this.globalData.projectInterface.registerComposition(comp)
      }
    }
  }

  BaseRenderer.prototype.getElementByPath = function (path) {
    var pathValue = path.shift()
    var element
    if (typeof pathValue === 'number') {
      element = this.elements[pathValue]
    } else {
      var i
      var len = this.elements.length
      for (i = 0; i < len; i += 1) {
        if (this.elements[i].data.nm === pathValue) {
          element = this.elements[i]
          break
        }
      }
    }
    if (path.length === 0) {
      return element
    }
    return element.getElementByPath(path)
  }

  BaseRenderer.prototype.setupGlobalData = function (animData, fontsContainer) {
    this.globalData.fontManager = new FontManager()
    this.globalData.fontManager.addChars(animData.chars)
    this.globalData.fontManager.addFonts(animData.fonts, fontsContainer)
    this.globalData.getAssetData = this.animationItem.getAssetData.bind(this.animationItem)
    this.globalData.getAssetsPath = this.animationItem.getAssetsPath.bind(this.animationItem)
    this.globalData.imageLoader = this.animationItem.imagePreloader
    this.globalData.audioController = this.animationItem.audioController
    this.globalData.frameId = 0
    this.globalData.frameRate = animData.fr
    this.globalData.nm = animData.nm
    this.globalData.compSize = {
      w: animData.w,
      h: animData.h,
    }
  }

  function TransformElement() {}

  TransformElement.prototype = {
    initTransform: function () {
      this.finalTransform = {
        mProp: this.data.ks ? TransformPropertyFactory.getTransformProperty(this, this.data.ks, this) : { o: 0 },
        _matMdf: false,
        _opMdf: false,
        mat: new Matrix(),
      }
      if (this.data.ao) {
        this.finalTransform.mProp.autoOriented = true
      }

      // TODO: check TYPE 11: Guided elements
      if (this.data.ty !== 11) {
        // this.createElements();
      }
    },
    renderTransform: function () {
      this.finalTransform._opMdf = this.finalTransform.mProp.o._mdf || this._isFirstFrame
      this.finalTransform._matMdf = this.finalTransform.mProp._mdf || this._isFirstFrame

      if (this.hierarchy) {
        var mat
        var finalMat = this.finalTransform.mat
        var i = 0
        var len = this.hierarchy.length
        // Checking if any of the transformation matrices in the hierarchy chain has changed.
        if (!this.finalTransform._matMdf) {
          while (i < len) {
            if (this.hierarchy[i].finalTransform.mProp._mdf) {
              this.finalTransform._matMdf = true
              break
            }
            i += 1
          }
        }

        if (this.finalTransform._matMdf) {
          mat = this.finalTransform.mProp.v.props
          finalMat.cloneFromProps(mat)
          for (i = 0; i < len; i += 1) {
            mat = this.hierarchy[i].finalTransform.mProp.v.props
            finalMat.transform(
              mat[0],
              mat[1],
              mat[2],
              mat[3],
              mat[4],
              mat[5],
              mat[6],
              mat[7],
              mat[8],
              mat[9],
              mat[10],
              mat[11],
              mat[12],
              mat[13],
              mat[14],
              mat[15],
            )
          }
        }
      }
    },
    globalToLocal: function (pt) {
      var transforms = []
      transforms.push(this.finalTransform)
      var flag = true
      var comp = this.comp
      while (flag) {
        if (comp.finalTransform) {
          if (comp.data.hasMask) {
            transforms.splice(0, 0, comp.finalTransform)
          }
          comp = comp.comp
        } else {
          flag = false
        }
      }
      var i
      var len = transforms.length
      var ptNew
      for (i = 0; i < len; i += 1) {
        ptNew = transforms[i].mat.applyToPointArray(0, 0, 0)
        // ptNew = transforms[i].mat.applyToPointArray(pt[0],pt[1],pt[2]);
        pt = [pt[0] - ptNew[0], pt[1] - ptNew[1], 0]
      }
      return pt
    },
    mHelper: new Matrix(),
  }

  function MaskElement(data, element, globalData) {
    this.data = data
    this.element = element
    this.globalData = globalData
    this.storedData = []
    this.masksProperties = this.data.masksProperties || []
    this.maskElement = null
    var defs = this.globalData.defs
    var i
    var len = this.masksProperties ? this.masksProperties.length : 0
    this.viewData = createSizedArray(len)
    this.solidPath = ''

    var path
    var properties = this.masksProperties
    var count = 0
    var currentMasks = []
    var j
    var jLen
    var layerId = createElementID()
    var rect
    var expansor
    var feMorph
    var x
    var maskType = 'clipPath'
    var maskRef = 'clip-path'
    for (i = 0; i < len; i += 1) {
      if (
        (properties[i].mode !== 'a' && properties[i].mode !== 'n') ||
        properties[i].inv ||
        properties[i].o.k !== 100 ||
        properties[i].o.x
      ) {
        maskType = 'mask'
        maskRef = 'mask'
      }

      if ((properties[i].mode === 's' || properties[i].mode === 'i') && count === 0) {
        rect = createNS('rect')
        rect.setAttribute('fill', '#ffffff')
        rect.setAttribute('width', this.element.comp.data.w || 0)
        rect.setAttribute('height', this.element.comp.data.h || 0)
        currentMasks.push(rect)
      } else {
        rect = null
      }

      path = createNS('path')
      if (properties[i].mode === 'n') {
        // TODO move this to a factory or to a constructor
        this.viewData[i] = {
          op: PropertyFactory.getProp(this.element, properties[i].o, 0, 0.01, this.element),
          prop: ShapePropertyFactory.getShapeProp(this.element, properties[i], 3),
          elem: path,
          lastPath: '',
        }
        defs.appendChild(path)
      } else {
        count += 1

        path.setAttribute('fill', properties[i].mode === 's' ? '#000000' : '#ffffff')
        path.setAttribute('clip-rule', 'nonzero')
        var filterID

        if (properties[i].x.k !== 0) {
          maskType = 'mask'
          maskRef = 'mask'
          x = PropertyFactory.getProp(this.element, properties[i].x, 0, null, this.element)
          filterID = createElementID()
          expansor = createNS('filter')
          expansor.setAttribute('id', filterID)
          feMorph = createNS('feMorphology')
          feMorph.setAttribute('operator', 'erode')
          feMorph.setAttribute('in', 'SourceGraphic')
          feMorph.setAttribute('radius', '0')
          expansor.appendChild(feMorph)
          defs.appendChild(expansor)
          path.setAttribute('stroke', properties[i].mode === 's' ? '#000000' : '#ffffff')
        } else {
          feMorph = null
          x = null
        }

        // TODO move this to a factory or to a constructor
        this.storedData[i] = {
          elem: path,
          x: x,
          expan: feMorph,
          lastPath: '',
          lastOperator: '',
          filterId: filterID,
          lastRadius: 0,
        }
        if (properties[i].mode === 'i') {
          jLen = currentMasks.length
          var g = createNS('g')
          for (j = 0; j < jLen; j += 1) {
            g.appendChild(currentMasks[j])
          }
          var mask = createNS('mask')
          mask.setAttribute('mask-type', 'alpha')
          mask.setAttribute('id', layerId + '_' + count)
          mask.appendChild(path)
          defs.appendChild(mask)
          g.setAttribute('mask', 'url(' + getLocationHref() + '#' + layerId + '_' + count + ')')

          currentMasks.length = 0
          currentMasks.push(g)
        } else {
          currentMasks.push(path)
        }
        if (properties[i].inv && !this.solidPath) {
          this.solidPath = this.createLayerSolidPath()
        }
        // TODO move this to a factory or to a constructor
        this.viewData[i] = {
          elem: path,
          lastPath: '',
          op: PropertyFactory.getProp(this.element, properties[i].o, 0, 0.01, this.element),
          prop: ShapePropertyFactory.getShapeProp(this.element, properties[i], 3),
          invRect: rect,
        }
        if (!this.viewData[i].prop.k) {
          this.drawPath(properties[i], this.viewData[i].prop.v, this.viewData[i])
        }
      }
    }

    this.maskElement = createNS(maskType)

    len = currentMasks.length
    for (i = 0; i < len; i += 1) {
      this.maskElement.appendChild(currentMasks[i])
    }

    if (count > 0) {
      this.maskElement.setAttribute('id', layerId)
      this.element.maskedElement.setAttribute(maskRef, 'url(' + getLocationHref() + '#' + layerId + ')')
      defs.appendChild(this.maskElement)
    }
    if (this.viewData.length) {
      this.element.addRenderableComponent(this)
    }
  }

  MaskElement.prototype.getMaskProperty = function (pos) {
    return this.viewData[pos].prop
  }

  MaskElement.prototype.renderFrame = function (isFirstFrame) {
    var finalMat = this.element.finalTransform.mat
    var i
    var len = this.masksProperties.length
    for (i = 0; i < len; i += 1) {
      if (this.viewData[i].prop._mdf || isFirstFrame) {
        this.drawPath(this.masksProperties[i], this.viewData[i].prop.v, this.viewData[i])
      }
      if (this.viewData[i].op._mdf || isFirstFrame) {
        this.viewData[i].elem.setAttribute('fill-opacity', this.viewData[i].op.v)
      }
      if (this.masksProperties[i].mode !== 'n') {
        if (this.viewData[i].invRect && (this.element.finalTransform.mProp._mdf || isFirstFrame)) {
          this.viewData[i].invRect.setAttribute('transform', finalMat.getInverseMatrix().to2dCSS())
        }
        if (this.storedData[i].x && (this.storedData[i].x._mdf || isFirstFrame)) {
          var feMorph = this.storedData[i].expan
          if (this.storedData[i].x.v < 0) {
            if (this.storedData[i].lastOperator !== 'erode') {
              this.storedData[i].lastOperator = 'erode'
              this.storedData[i].elem.setAttribute(
                'filter',
                'url(' + getLocationHref() + '#' + this.storedData[i].filterId + ')',
              )
            }
            feMorph.setAttribute('radius', -this.storedData[i].x.v)
          } else {
            if (this.storedData[i].lastOperator !== 'dilate') {
              this.storedData[i].lastOperator = 'dilate'
              this.storedData[i].elem.setAttribute('filter', null)
            }
            this.storedData[i].elem.setAttribute('stroke-width', this.storedData[i].x.v * 2)
          }
        }
      }
    }
  }

  MaskElement.prototype.getMaskelement = function () {
    return this.maskElement
  }

  MaskElement.prototype.createLayerSolidPath = function () {
    var path = 'M0,0 '
    path += ' h' + this.globalData.compSize.w
    path += ' v' + this.globalData.compSize.h
    path += ' h-' + this.globalData.compSize.w
    path += ' v-' + this.globalData.compSize.h + ' '
    return path
  }

  MaskElement.prototype.drawPath = function (pathData, pathNodes, viewData) {
    var pathString = ' M' + pathNodes.v[0][0] + ',' + pathNodes.v[0][1]
    var i
    var len
    len = pathNodes._length
    for (i = 1; i < len; i += 1) {
      // pathString += " C"+pathNodes.o[i-1][0]+','+pathNodes.o[i-1][1] + " "+pathNodes.i[i][0]+','+pathNodes.i[i][1] + " "+pathNodes.v[i][0]+','+pathNodes.v[i][1];
      pathString +=
        ' C' +
        pathNodes.o[i - 1][0] +
        ',' +
        pathNodes.o[i - 1][1] +
        ' ' +
        pathNodes.i[i][0] +
        ',' +
        pathNodes.i[i][1] +
        ' ' +
        pathNodes.v[i][0] +
        ',' +
        pathNodes.v[i][1]
    }
    // pathString += " C"+pathNodes.o[i-1][0]+','+pathNodes.o[i-1][1] + " "+pathNodes.i[0][0]+','+pathNodes.i[0][1] + " "+pathNodes.v[0][0]+','+pathNodes.v[0][1];
    if (pathNodes.c && len > 1) {
      pathString +=
        ' C' +
        pathNodes.o[i - 1][0] +
        ',' +
        pathNodes.o[i - 1][1] +
        ' ' +
        pathNodes.i[0][0] +
        ',' +
        pathNodes.i[0][1] +
        ' ' +
        pathNodes.v[0][0] +
        ',' +
        pathNodes.v[0][1]
    }
    // pathNodes.__renderedString = pathString;

    if (viewData.lastPath !== pathString) {
      var pathShapeValue = ''
      if (viewData.elem) {
        if (pathNodes.c) {
          pathShapeValue = pathData.inv ? this.solidPath + pathString : pathString
        }
        viewData.elem.setAttribute('d', pathShapeValue)
      }
      viewData.lastPath = pathString
    }
  }

  MaskElement.prototype.destroy = function () {
    this.element = null
    this.globalData = null
    this.maskElement = null
    this.data = null
    this.masksProperties = null
  }

  const filtersFactory = (function () {
    var ob = {}
    ob.createFilter = createFilter
    ob.createAlphaToLuminanceFilter = createAlphaToLuminanceFilter

    function createFilter(filId, skipCoordinates) {
      var fil = createNS('filter')
      fil.setAttribute('id', filId)
      if (skipCoordinates !== true) {
        fil.setAttribute('filterUnits', 'objectBoundingBox')
        fil.setAttribute('x', '0%')
        fil.setAttribute('y', '0%')
        fil.setAttribute('width', '100%')
        fil.setAttribute('height', '100%')
      }
      return fil
    }

    function createAlphaToLuminanceFilter() {
      var feColorMatrix = createNS('feColorMatrix')
      feColorMatrix.setAttribute('type', 'matrix')
      feColorMatrix.setAttribute('color-interpolation-filters', 'sRGB')
      feColorMatrix.setAttribute('values', '0 0 0 1 0  0 0 0 1 0  0 0 0 1 0  0 0 0 1 1')
      return feColorMatrix
    }

    return ob
  })()

  const featureSupport = (function () {
    var ob = {
      maskType: true,
    }
    if (
      /MSIE 10/i.test(navigator.userAgent) ||
      /MSIE 9/i.test(navigator.userAgent) ||
      /rv:11.0/i.test(navigator.userAgent) ||
      /Edge\/\d./i.test(navigator.userAgent)
    ) {
      ob.maskType = false
    }
    return ob
  })()

  var registeredEffects = {}
  var idPrefix = 'filter_result_'

  function SVGEffects(elem) {
    var i
    var source = 'SourceGraphic'
    var len = elem.data.ef ? elem.data.ef.length : 0
    var filId = createElementID()
    var fil = filtersFactory.createFilter(filId, true)
    var count = 0
    this.filters = []
    var filterManager
    for (i = 0; i < len; i += 1) {
      filterManager = null
      var type = elem.data.ef[i].ty
      if (registeredEffects[type]) {
        var Effect = registeredEffects[type].effect
        filterManager = new Effect(fil, elem.effectsManager.effectElements[i], elem, idPrefix + count, source)
        source = idPrefix + count
        if (registeredEffects[type].countsAsEffect) {
          count += 1
        }
      }
      if (filterManager) {
        this.filters.push(filterManager)
      }
    }
    if (count) {
      elem.globalData.defs.appendChild(fil)
      elem.layerElement.setAttribute('filter', 'url(' + getLocationHref() + '#' + filId + ')')
    }
    if (this.filters.length) {
      elem.addRenderableComponent(this)
    }
  }

  SVGEffects.prototype.renderFrame = function (_isFirstFrame) {
    var i
    var len = this.filters.length
    for (i = 0; i < len; i += 1) {
      this.filters[i].renderFrame(_isFirstFrame)
    }
  }

  function registerEffect(id, effect, countsAsEffect) {
    registeredEffects[id] = {
      effect,
      countsAsEffect,
    }
  }

  function SVGBaseElement() {}

  SVGBaseElement.prototype = {
    initRendererElement: function () {
      this.layerElement = createNS('g')
    },
    createContainerElements: function () {
      this.matteElement = createNS('g')
      this.transformedElement = this.layerElement
      this.maskedElement = this.layerElement
      this._sizeChanged = false
      var layerElementParent = null
      // If this layer acts as a mask for the following layer
      var filId
      var fil
      var gg
      if (this.data.td) {
        if (this.data.td == 3 || this.data.td == 1) {
          // eslint-disable-line eqeqeq
          var masker = createNS('mask')
          masker.setAttribute('id', this.layerId)
          masker.setAttribute('mask-type', this.data.td == 3 ? 'luminance' : 'alpha') // eslint-disable-line eqeqeq
          masker.appendChild(this.layerElement)
          layerElementParent = masker
          this.globalData.defs.appendChild(masker)
          // This is only for IE and Edge when mask if of type alpha
          if (!featureSupport.maskType && this.data.td == 1) {
            // eslint-disable-line eqeqeq
            masker.setAttribute('mask-type', 'luminance')
            filId = createElementID()
            fil = filtersFactory.createFilter(filId)
            this.globalData.defs.appendChild(fil)
            fil.appendChild(filtersFactory.createAlphaToLuminanceFilter())
            gg = createNS('g')
            gg.appendChild(this.layerElement)
            layerElementParent = gg
            masker.appendChild(gg)
            gg.setAttribute('filter', 'url(' + getLocationHref() + '#' + filId + ')')
          }
        } else if (this.data.td == 2) {
          // eslint-disable-line eqeqeq
          var maskGroup = createNS('mask')
          maskGroup.setAttribute('id', this.layerId)
          maskGroup.setAttribute('mask-type', 'alpha')
          var maskGrouper = createNS('g')
          maskGroup.appendChild(maskGrouper)
          filId = createElementID()
          fil = filtersFactory.createFilter(filId)
          /// /

          // This solution doesn't work on Android when meta tag with viewport attribute is set
          /* var feColorMatrix = createNS('feColorMatrix');
                feColorMatrix.setAttribute('type', 'matrix');
                feColorMatrix.setAttribute('color-interpolation-filters', 'sRGB');
                feColorMatrix.setAttribute('values','1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 -1 1');
                fil.appendChild(feColorMatrix); */
          /// /
          var feCTr = createNS('feComponentTransfer')
          feCTr.setAttribute('in', 'SourceGraphic')
          fil.appendChild(feCTr)
          var feFunc = createNS('feFuncA')
          feFunc.setAttribute('type', 'table')
          feFunc.setAttribute('tableValues', '1.0 0.0')
          feCTr.appendChild(feFunc)
          /// /
          this.globalData.defs.appendChild(fil)
          var alphaRect = createNS('rect')
          alphaRect.setAttribute('width', this.comp.data.w)
          alphaRect.setAttribute('height', this.comp.data.h)
          alphaRect.setAttribute('x', '0')
          alphaRect.setAttribute('y', '0')
          alphaRect.setAttribute('fill', '#ffffff')
          alphaRect.setAttribute('opacity', '0')
          maskGrouper.setAttribute('filter', 'url(' + getLocationHref() + '#' + filId + ')')
          maskGrouper.appendChild(alphaRect)
          maskGrouper.appendChild(this.layerElement)
          layerElementParent = maskGrouper
          if (!featureSupport.maskType) {
            maskGroup.setAttribute('mask-type', 'luminance')
            fil.appendChild(filtersFactory.createAlphaToLuminanceFilter())
            gg = createNS('g')
            maskGrouper.appendChild(alphaRect)
            gg.appendChild(this.layerElement)
            layerElementParent = gg
            maskGrouper.appendChild(gg)
          }
          this.globalData.defs.appendChild(maskGroup)
        }
      } else if (this.data.tt) {
        this.matteElement.appendChild(this.layerElement)
        layerElementParent = this.matteElement
        this.baseElement = this.matteElement
      } else {
        this.baseElement = this.layerElement
      }
      if (this.data.ln) {
        this.layerElement.setAttribute('id', this.data.ln)
      }
      if (this.data.cl) {
        this.layerElement.setAttribute('class', this.data.cl)
      }
      // Clipping compositions to hide content that exceeds boundaries. If collapsed transformations is on, component should not be clipped
      if (this.data.ty === 0 && !this.data.hd) {
        var cp = createNS('clipPath')
        var pt = createNS('path')
        pt.setAttribute(
          'd',
          'M0,0 L' + this.data.w + ',0 L' + this.data.w + ',' + this.data.h + ' L0,' + this.data.h + 'z',
        )
        var clipId = createElementID()
        cp.setAttribute('id', clipId)
        cp.appendChild(pt)
        this.globalData.defs.appendChild(cp)

        if (this.checkMasks()) {
          var cpGroup = createNS('g')
          cpGroup.setAttribute('clip-path', 'url(' + getLocationHref() + '#' + clipId + ')')
          cpGroup.appendChild(this.layerElement)
          this.transformedElement = cpGroup
          if (layerElementParent) {
            layerElementParent.appendChild(this.transformedElement)
          } else {
            this.baseElement = this.transformedElement
          }
        } else {
          this.layerElement.setAttribute('clip-path', 'url(' + getLocationHref() + '#' + clipId + ')')
        }
      }
      if (this.data.bm !== 0) {
        this.setBlendMode()
      }
    },
    renderElement: function () {
      if (this.finalTransform._matMdf) {
        this.transformedElement.setAttribute('transform', this.finalTransform.mat.to2dCSS())
      }
      if (this.finalTransform._opMdf) {
        this.transformedElement.setAttribute('opacity', this.finalTransform.mProp.o.v)
      }
    },
    destroyBaseElement: function () {
      this.layerElement = null
      this.matteElement = null
      this.maskManager.destroy()
    },
    getBaseElement: function () {
      if (this.data.hd) {
        return null
      }
      return this.baseElement
    },
    createRenderableComponents: function () {
      this.maskManager = new MaskElement(this.data, this, this.globalData)
      this.renderableEffectsManager = new SVGEffects(this)
    },
    setMatte: function (id) {
      if (!this.matteElement) {
        return
      }
      this.matteElement.setAttribute('mask', 'url(' + getLocationHref() + '#' + id + ')')
    },
  }

  /**
   * @file
   * Handles AE's layer parenting property.
   *
   */

  function HierarchyElement() {}

  HierarchyElement.prototype = {
    /**
     * @function
     * Initializes hierarchy properties
     *
     */
    initHierarchy: function () {
      // element's parent list
      this.hierarchy = []
      // if element is parent of another layer _isParent will be true
      this._isParent = false
      this.checkParenting()
    },
    /**
     * @function
     * Sets layer's hierarchy.
     * @param {array} hierarch
     * layer's parent list
     *
     */
    setHierarchy: function (hierarchy) {
      this.hierarchy = hierarchy
    },
    /**
     * @function
     * Sets layer as parent.
     *
     */
    setAsParent: function () {
      this._isParent = true
    },
    /**
     * @function
     * Searches layer's parenting chain
     *
     */
    checkParenting: function () {
      if (this.data.parent !== undefined) {
        this.comp.buildElementParenting(this, this.data.parent, [])
      }
    },
  }

  function RenderableDOMElement() {}

  ;(function () {
    var _prototype = {
      initElement: function (data, globalData, comp) {
        this.initFrame()
        this.initBaseData(data, globalData, comp)
        this.initTransform(data, globalData, comp)
        this.initHierarchy()
        this.initRenderable()
        this.initRendererElement()
        this.createContainerElements()
        this.createRenderableComponents()
        this.createContent()
        this.hide()
      },
      hide: function () {
        // console.log('HIDE', this);
        if (!this.hidden && (!this.isInRange || this.isTransparent)) {
          var elem = this.baseElement || this.layerElement
          elem.style.display = 'none'
          this.hidden = true
        }
      },
      show: function () {
        // console.log('SHOW', this);
        if (this.isInRange && !this.isTransparent) {
          if (!this.data.hd) {
            var elem = this.baseElement || this.layerElement
            elem.style.display = 'block'
          }
          this.hidden = false
          this._isFirstFrame = true
        }
      },
      renderFrame: function () {
        // If it is exported as hidden (data.hd === true) no need to render
        // If it is not visible no need to render
        if (this.data.hd || this.hidden) {
          return
        }
        this.renderTransform()
        this.renderRenderable()
        this.renderElement()
        this.renderInnerContent()
        if (this._isFirstFrame) {
          this._isFirstFrame = false
        }
      },
      renderInnerContent: function () {},
      prepareFrame: function (num) {
        this._mdf = false
        this.prepareRenderableFrame(num)
        this.prepareProperties(num, this.isInRange)
        this.checkTransparency()
      },
      destroy: function () {
        this.innerElem = null
        this.destroyBaseElement()
      },
    }
    extendPrototype([RenderableElement, createProxyFunction(_prototype)], RenderableDOMElement)
  })()

  function IImageElement(data, globalData, comp) {
    this.assetData = globalData.getAssetData(data.refId)
    this.initElement(data, globalData, comp)
    this.sourceRect = {
      top: 0,
      left: 0,
      width: this.assetData.w,
      height: this.assetData.h,
    }
  }

  extendPrototype(
    [BaseElement, TransformElement, SVGBaseElement, HierarchyElement, FrameElement, RenderableDOMElement],
    IImageElement,
  )

  IImageElement.prototype.createContent = function () {
    var assetPath = this.globalData.getAssetsPath(this.assetData)

    this.innerElem = createNS('image')
    this.innerElem.setAttribute('width', this.assetData.w + 'px')
    this.innerElem.setAttribute('height', this.assetData.h + 'px')
    this.innerElem.setAttribute(
      'preserveAspectRatio',
      this.assetData.pr || this.globalData.renderConfig.imagePreserveAspectRatio,
    )
    this.innerElem.setAttributeNS('http://www.w3.org/1999/xlink', 'href', assetPath)

    this.layerElement.appendChild(this.innerElem)
  }

  IImageElement.prototype.sourceRectAtTime = function () {
    return this.sourceRect
  }

  function ProcessedElement(element, position) {
    this.elem = element
    this.pos = position
  }

  function IShapeElement() {}

  IShapeElement.prototype = {
    addShapeToModifiers: function (data) {
      var i
      var len = this.shapeModifiers.length
      for (i = 0; i < len; i += 1) {
        this.shapeModifiers[i].addShape(data)
      }
    },
    isShapeInAnimatedModifiers: function (data) {
      var i = 0
      var len = this.shapeModifiers.length
      while (i < len) {
        if (this.shapeModifiers[i].isAnimatedWithShape(data)) {
          return true
        }
      }
      return false
    },
    renderModifiers: function () {
      if (!this.shapeModifiers.length) {
        return
      }
      var i
      var len = this.shapes.length
      for (i = 0; i < len; i += 1) {
        this.shapes[i].sh.reset()
      }

      len = this.shapeModifiers.length
      var shouldBreakProcess
      for (i = len - 1; i >= 0; i -= 1) {
        shouldBreakProcess = this.shapeModifiers[i].processShapes(this._isFirstFrame)
        // workaround to fix cases where a repeater resets the shape so the following processes get called twice
        // TODO: find a better solution for this
        if (shouldBreakProcess) {
          break
        }
      }
    },

    searchProcessedElement: function (elem) {
      var elements = this.processedElements
      var i = 0
      var len = elements.length
      while (i < len) {
        if (elements[i].elem === elem) {
          return elements[i].pos
        }
        i += 1
      }
      return 0
    },
    addProcessedElement: function (elem, pos) {
      var elements = this.processedElements
      var i = elements.length
      while (i) {
        i -= 1
        if (elements[i].elem === elem) {
          elements[i].pos = pos
          return
        }
      }
      elements.push(new ProcessedElement(elem, pos))
    },
    prepareFrame: function (num) {
      this.prepareRenderableFrame(num)
      this.prepareProperties(num, this.isInRange)
    },
  }

  const lineCapEnum = {
    1: 'butt',
    2: 'round',
    3: 'square',
  }

  const lineJoinEnum = {
    1: 'miter',
    2: 'round',
    3: 'bevel',
  }

  function SVGShapeData(transformers, level, shape) {
    this.caches = []
    this.styles = []
    this.transformers = transformers
    this.lStr = ''
    this.sh = shape
    this.lvl = level
    // TODO find if there are some cases where _isAnimated can be false.
    // For now, since shapes add up with other shapes. They have to be calculated every time.
    // One way of finding out is checking if all styles associated to this shape depend only of this shape
    this._isAnimated = !!shape.k
    // TODO: commenting this for now since all shapes are animated
    var i = 0
    var len = transformers.length
    while (i < len) {
      if (transformers[i].mProps.dynamicProperties.length) {
        this._isAnimated = true
        break
      }
      i += 1
    }
  }

  SVGShapeData.prototype.setAsAnimated = function () {
    this._isAnimated = true
  }

  function SVGStyleData(data, level) {
    this.data = data
    this.type = data.ty
    this.d = ''
    this.lvl = level
    this._mdf = false
    this.closed = data.hd === true
    this.pElem = createNS('path')
    this.msElem = null
  }

  SVGStyleData.prototype.reset = function () {
    this.d = ''
    this._mdf = false
  }

  function DashProperty(elem, data, renderer, container) {
    this.elem = elem
    this.frameId = -1
    this.dataProps = createSizedArray(data.length)
    this.renderer = renderer
    this.k = false
    this.dashStr = ''
    this.dashArray = createTypedArray('float32', data.length ? data.length - 1 : 0)
    this.dashoffset = createTypedArray('float32', 1)
    this.initDynamicPropertyContainer(container)
    var i
    var len = data.length || 0
    var prop
    for (i = 0; i < len; i += 1) {
      prop = PropertyFactory.getProp(elem, data[i].v, 0, 0, this)
      this.k = prop.k || this.k
      this.dataProps[i] = { n: data[i].n, p: prop }
    }
    if (!this.k) {
      this.getValue(true)
    }
    this._isAnimated = this.k
  }

  DashProperty.prototype.getValue = function (forceRender) {
    if (this.elem.globalData.frameId === this.frameId && !forceRender) {
      return
    }
    this.frameId = this.elem.globalData.frameId
    this.iterateDynamicProperties()
    this._mdf = this._mdf || forceRender
    if (this._mdf) {
      var i = 0
      var len = this.dataProps.length
      if (this.renderer === 'svg') {
        this.dashStr = ''
      }
      for (i = 0; i < len; i += 1) {
        if (this.dataProps[i].n !== 'o') {
          if (this.renderer === 'svg') {
            this.dashStr += ' ' + this.dataProps[i].p.v
          } else {
            this.dashArray[i] = this.dataProps[i].p.v
          }
        } else {
          this.dashoffset[0] = this.dataProps[i].p.v
        }
      }
    }
  }
  extendPrototype([DynamicPropertyContainer], DashProperty)

  function SVGStrokeStyleData(elem, data, styleOb) {
    this.initDynamicPropertyContainer(elem)
    this.getValue = this.iterateDynamicProperties
    this.o = PropertyFactory.getProp(elem, data.o, 0, 0.01, this)
    this.w = PropertyFactory.getProp(elem, data.w, 0, null, this)
    this.d = new DashProperty(elem, data.d || {}, 'svg', this)
    this.c = PropertyFactory.getProp(elem, data.c, 1, 255, this)
    this.style = styleOb
    this._isAnimated = !!this._isAnimated
  }

  extendPrototype([DynamicPropertyContainer], SVGStrokeStyleData)

  function SVGFillStyleData(elem, data, styleOb) {
    this.initDynamicPropertyContainer(elem)
    this.getValue = this.iterateDynamicProperties
    this.o = PropertyFactory.getProp(elem, data.o, 0, 0.01, this)
    this.c = PropertyFactory.getProp(elem, data.c, 1, 255, this)
    this.style = styleOb
  }

  extendPrototype([DynamicPropertyContainer], SVGFillStyleData)

  function SVGNoStyleData(elem, data, styleOb) {
    this.initDynamicPropertyContainer(elem)
    this.getValue = this.iterateDynamicProperties
    this.style = styleOb
  }

  extendPrototype([DynamicPropertyContainer], SVGNoStyleData)

  function GradientProperty(elem, data, container) {
    this.data = data
    this.c = createTypedArray('uint8c', data.p * 4)
    var cLength = data.k.k[0].s ? data.k.k[0].s.length - data.p * 4 : data.k.k.length - data.p * 4
    this.o = createTypedArray('float32', cLength)
    this._cmdf = false
    this._omdf = false
    this._collapsable = this.checkCollapsable()
    this._hasOpacity = cLength
    this.initDynamicPropertyContainer(container)
    this.prop = PropertyFactory.getProp(elem, data.k, 1, null, this)
    this.k = this.prop.k
    this.getValue(true)
  }

  GradientProperty.prototype.comparePoints = function (values, points) {
    var i = 0
    var len = this.o.length / 2
    var diff
    while (i < len) {
      diff = Math.abs(values[i * 4] - values[points * 4 + i * 2])
      if (diff > 0.01) {
        return false
      }
      i += 1
    }
    return true
  }

  GradientProperty.prototype.checkCollapsable = function () {
    if (this.o.length / 2 !== this.c.length / 4) {
      return false
    }
    if (this.data.k.k[0].s) {
      var i = 0
      var len = this.data.k.k.length
      while (i < len) {
        if (!this.comparePoints(this.data.k.k[i].s, this.data.p)) {
          return false
        }
        i += 1
      }
    } else if (!this.comparePoints(this.data.k.k, this.data.p)) {
      return false
    }
    return true
  }

  GradientProperty.prototype.getValue = function (forceRender) {
    this.prop.getValue()
    this._mdf = false
    this._cmdf = false
    this._omdf = false
    if (this.prop._mdf || forceRender) {
      var i
      var len = this.data.p * 4
      var mult
      var val
      for (i = 0; i < len; i += 1) {
        mult = i % 4 === 0 ? 100 : 255
        val = Math.round(this.prop.v[i] * mult)
        if (this.c[i] !== val) {
          this.c[i] = val
          this._cmdf = !forceRender
        }
      }
      if (this.o.length) {
        len = this.prop.v.length
        for (i = this.data.p * 4; i < len; i += 1) {
          mult = i % 2 === 0 ? 100 : 1
          val = i % 2 === 0 ? Math.round(this.prop.v[i] * 100) : this.prop.v[i]
          if (this.o[i - this.data.p * 4] !== val) {
            this.o[i - this.data.p * 4] = val
            this._omdf = !forceRender
          }
        }
      }
      this._mdf = !forceRender
    }
  }

  extendPrototype([DynamicPropertyContainer], GradientProperty)

  function SVGGradientFillStyleData(elem, data, styleOb) {
    this.initDynamicPropertyContainer(elem)
    this.getValue = this.iterateDynamicProperties
    this.initGradientData(elem, data, styleOb)
  }

  SVGGradientFillStyleData.prototype.initGradientData = function (elem, data, styleOb) {
    this.o = PropertyFactory.getProp(elem, data.o, 0, 0.01, this)
    this.s = PropertyFactory.getProp(elem, data.s, 1, null, this)
    this.e = PropertyFactory.getProp(elem, data.e, 1, null, this)
    this.h = PropertyFactory.getProp(elem, data.h || { k: 0 }, 0, 0.01, this)
    this.a = PropertyFactory.getProp(elem, data.a || { k: 0 }, 0, degToRads, this)
    this.g = new GradientProperty(elem, data.g, this)
    this.style = styleOb
    this.stops = []
    this.setGradientData(styleOb.pElem, data)
    this.setGradientOpacity(data, styleOb)
    this._isAnimated = !!this._isAnimated
  }

  SVGGradientFillStyleData.prototype.setGradientData = function (pathElement, data) {
    var gradientId = createElementID()
    var gfill = createNS(data.t === 1 ? 'linearGradient' : 'radialGradient')
    gfill.setAttribute('id', gradientId)
    gfill.setAttribute('spreadMethod', 'pad')
    gfill.setAttribute('gradientUnits', 'userSpaceOnUse')
    var stops = []
    var stop
    var j
    var jLen
    jLen = data.g.p * 4
    for (j = 0; j < jLen; j += 4) {
      stop = createNS('stop')
      gfill.appendChild(stop)
      stops.push(stop)
    }
    pathElement.setAttribute(data.ty === 'gf' ? 'fill' : 'stroke', 'url(' + getLocationHref() + '#' + gradientId + ')')
    this.gf = gfill
    this.cst = stops
  }

  SVGGradientFillStyleData.prototype.setGradientOpacity = function (data, styleOb) {
    if (this.g._hasOpacity && !this.g._collapsable) {
      var stop
      var j
      var jLen
      var mask = createNS('mask')
      var maskElement = createNS('path')
      mask.appendChild(maskElement)
      var opacityId = createElementID()
      var maskId = createElementID()
      mask.setAttribute('id', maskId)
      var opFill = createNS(data.t === 1 ? 'linearGradient' : 'radialGradient')
      opFill.setAttribute('id', opacityId)
      opFill.setAttribute('spreadMethod', 'pad')
      opFill.setAttribute('gradientUnits', 'userSpaceOnUse')
      jLen = data.g.k.k[0].s ? data.g.k.k[0].s.length : data.g.k.k.length
      var stops = this.stops
      for (j = data.g.p * 4; j < jLen; j += 2) {
        stop = createNS('stop')
        stop.setAttribute('stop-color', 'rgb(255,255,255)')
        opFill.appendChild(stop)
        stops.push(stop)
      }
      maskElement.setAttribute(data.ty === 'gf' ? 'fill' : 'stroke', 'url(' + getLocationHref() + '#' + opacityId + ')')
      if (data.ty === 'gs') {
        maskElement.setAttribute('stroke-linecap', lineCapEnum[data.lc || 2])
        maskElement.setAttribute('stroke-linejoin', lineJoinEnum[data.lj || 2])
        if (data.lj === 1) {
          maskElement.setAttribute('stroke-miterlimit', data.ml)
        }
      }
      this.of = opFill
      this.ms = mask
      this.ost = stops
      this.maskId = maskId
      styleOb.msElem = maskElement
    }
  }

  extendPrototype([DynamicPropertyContainer], SVGGradientFillStyleData)

  function SVGGradientStrokeStyleData(elem, data, styleOb) {
    this.initDynamicPropertyContainer(elem)
    this.getValue = this.iterateDynamicProperties
    this.w = PropertyFactory.getProp(elem, data.w, 0, null, this)
    this.d = new DashProperty(elem, data.d || {}, 'svg', this)
    this.initGradientData(elem, data, styleOb)
    this._isAnimated = !!this._isAnimated
  }

  extendPrototype([SVGGradientFillStyleData, DynamicPropertyContainer], SVGGradientStrokeStyleData)

  function ShapeGroupData() {
    this.it = []
    this.prevViewData = []
    this.gr = createNS('g')
  }

  function SVGTransformData(mProps, op, container) {
    this.transform = {
      mProps: mProps,
      op: op,
      container: container,
    }
    this.elements = []
    this._isAnimated = this.transform.mProps.dynamicProperties.length || this.transform.op.effectsSequence.length
  }

  const buildShapeString = function (pathNodes, length, closed, mat) {
    if (length === 0) {
      return ''
    }
    var _o = pathNodes.o
    var _i = pathNodes.i
    var _v = pathNodes.v
    var i
    var shapeString = ' M' + mat.applyToPointStringified(_v[0][0], _v[0][1])
    for (i = 1; i < length; i += 1) {
      shapeString +=
        ' C' +
        mat.applyToPointStringified(_o[i - 1][0], _o[i - 1][1]) +
        ' ' +
        mat.applyToPointStringified(_i[i][0], _i[i][1]) +
        ' ' +
        mat.applyToPointStringified(_v[i][0], _v[i][1])
    }
    if (closed && length) {
      shapeString +=
        ' C' +
        mat.applyToPointStringified(_o[i - 1][0], _o[i - 1][1]) +
        ' ' +
        mat.applyToPointStringified(_i[0][0], _i[0][1]) +
        ' ' +
        mat.applyToPointStringified(_v[0][0], _v[0][1])
      shapeString += 'z'
    }
    return shapeString
  }

  const SVGElementsRenderer = (function () {
    var _identityMatrix = new Matrix()
    var _matrixHelper = new Matrix()

    var ob = {
      createRenderFunction: createRenderFunction,
    }

    function createRenderFunction(data) {
      switch (data.ty) {
        case 'fl':
          return renderFill
        case 'gf':
          return renderGradient
        case 'gs':
          return renderGradientStroke
        case 'st':
          return renderStroke
        case 'sh':
        case 'el':
        case 'rc':
        case 'sr':
          return renderPath
        case 'tr':
          return renderContentTransform
        case 'no':
          return renderNoop
        default:
          return null
      }
    }

    function renderContentTransform(styleData, itemData, isFirstFrame) {
      if (isFirstFrame || itemData.transform.op._mdf) {
        itemData.transform.container.setAttribute('opacity', itemData.transform.op.v)
      }
      if (isFirstFrame || itemData.transform.mProps._mdf) {
        itemData.transform.container.setAttribute('transform', itemData.transform.mProps.v.to2dCSS())
      }
    }

    function renderNoop() {}

    function renderPath(styleData, itemData, isFirstFrame) {
      var j
      var jLen
      var pathStringTransformed
      var redraw
      var pathNodes
      var l
      var lLen = itemData.styles.length
      var lvl = itemData.lvl
      var paths
      var mat
      var props
      var iterations
      var k
      for (l = 0; l < lLen; l += 1) {
        redraw = itemData.sh._mdf || isFirstFrame
        if (itemData.styles[l].lvl < lvl) {
          mat = _matrixHelper.reset()
          iterations = lvl - itemData.styles[l].lvl
          k = itemData.transformers.length - 1
          while (!redraw && iterations > 0) {
            redraw = itemData.transformers[k].mProps._mdf || redraw
            iterations -= 1
            k -= 1
          }
          if (redraw) {
            iterations = lvl - itemData.styles[l].lvl
            k = itemData.transformers.length - 1
            while (iterations > 0) {
              props = itemData.transformers[k].mProps.v.props
              mat.transform(
                props[0],
                props[1],
                props[2],
                props[3],
                props[4],
                props[5],
                props[6],
                props[7],
                props[8],
                props[9],
                props[10],
                props[11],
                props[12],
                props[13],
                props[14],
                props[15],
              )
              iterations -= 1
              k -= 1
            }
          }
        } else {
          mat = _identityMatrix
        }
        paths = itemData.sh.paths
        jLen = paths._length
        if (redraw) {
          pathStringTransformed = ''
          for (j = 0; j < jLen; j += 1) {
            pathNodes = paths.shapes[j]
            if (pathNodes && pathNodes._length) {
              pathStringTransformed += buildShapeString(pathNodes, pathNodes._length, pathNodes.c, mat)
            }
          }
          itemData.caches[l] = pathStringTransformed
        } else {
          pathStringTransformed = itemData.caches[l]
        }
        itemData.styles[l].d += styleData.hd === true ? '' : pathStringTransformed
        itemData.styles[l]._mdf = redraw || itemData.styles[l]._mdf
      }
    }

    function renderFill(styleData, itemData, isFirstFrame) {
      var styleElem = itemData.style

      if (itemData.c._mdf || isFirstFrame) {
        styleElem.pElem.setAttribute(
          'fill',
          'rgb(' + bmFloor(itemData.c.v[0]) + ',' + bmFloor(itemData.c.v[1]) + ',' + bmFloor(itemData.c.v[2]) + ')',
        )
      }
      if (itemData.o._mdf || isFirstFrame) {
        styleElem.pElem.setAttribute('fill-opacity', itemData.o.v)
      }
    }

    function renderGradientStroke(styleData, itemData, isFirstFrame) {
      renderGradient(styleData, itemData, isFirstFrame)
      renderStroke(styleData, itemData, isFirstFrame)
    }

    function renderGradient(styleData, itemData, isFirstFrame) {
      var gfill = itemData.gf
      var hasOpacity = itemData.g._hasOpacity
      var pt1 = itemData.s.v
      var pt2 = itemData.e.v

      if (itemData.o._mdf || isFirstFrame) {
        var attr = styleData.ty === 'gf' ? 'fill-opacity' : 'stroke-opacity'
        itemData.style.pElem.setAttribute(attr, itemData.o.v)
      }
      if (itemData.s._mdf || isFirstFrame) {
        var attr1 = styleData.t === 1 ? 'x1' : 'cx'
        var attr2 = attr1 === 'x1' ? 'y1' : 'cy'
        gfill.setAttribute(attr1, pt1[0])
        gfill.setAttribute(attr2, pt1[1])
        if (hasOpacity && !itemData.g._collapsable) {
          itemData.of.setAttribute(attr1, pt1[0])
          itemData.of.setAttribute(attr2, pt1[1])
        }
      }
      var stops
      var i
      var len
      var stop
      if (itemData.g._cmdf || isFirstFrame) {
        stops = itemData.cst
        var cValues = itemData.g.c
        len = stops.length
        for (i = 0; i < len; i += 1) {
          stop = stops[i]
          stop.setAttribute('offset', cValues[i * 4] + '%')
          stop.setAttribute(
            'stop-color',
            'rgb(' + cValues[i * 4 + 1] + ',' + cValues[i * 4 + 2] + ',' + cValues[i * 4 + 3] + ')',
          )
        }
      }
      if (hasOpacity && (itemData.g._omdf || isFirstFrame)) {
        var oValues = itemData.g.o
        if (itemData.g._collapsable) {
          stops = itemData.cst
        } else {
          stops = itemData.ost
        }
        len = stops.length
        for (i = 0; i < len; i += 1) {
          stop = stops[i]
          if (!itemData.g._collapsable) {
            stop.setAttribute('offset', oValues[i * 2] + '%')
          }
          stop.setAttribute('stop-opacity', oValues[i * 2 + 1])
        }
      }
      if (styleData.t === 1) {
        if (itemData.e._mdf || isFirstFrame) {
          gfill.setAttribute('x2', pt2[0])
          gfill.setAttribute('y2', pt2[1])
          if (hasOpacity && !itemData.g._collapsable) {
            itemData.of.setAttribute('x2', pt2[0])
            itemData.of.setAttribute('y2', pt2[1])
          }
        }
      } else {
        var rad
        if (itemData.s._mdf || itemData.e._mdf || isFirstFrame) {
          rad = Math.sqrt(Math.pow(pt1[0] - pt2[0], 2) + Math.pow(pt1[1] - pt2[1], 2))
          gfill.setAttribute('r', rad)
          if (hasOpacity && !itemData.g._collapsable) {
            itemData.of.setAttribute('r', rad)
          }
        }
        if (itemData.e._mdf || itemData.h._mdf || itemData.a._mdf || isFirstFrame) {
          if (!rad) {
            rad = Math.sqrt(Math.pow(pt1[0] - pt2[0], 2) + Math.pow(pt1[1] - pt2[1], 2))
          }
          var ang = Math.atan2(pt2[1] - pt1[1], pt2[0] - pt1[0])

          var percent = itemData.h.v
          if (percent >= 1) {
            percent = 0.99
          } else if (percent <= -1) {
            percent = -0.99
          }
          var dist = rad * percent
          var x = Math.cos(ang + itemData.a.v) * dist + pt1[0]
          var y = Math.sin(ang + itemData.a.v) * dist + pt1[1]
          gfill.setAttribute('fx', x)
          gfill.setAttribute('fy', y)
          if (hasOpacity && !itemData.g._collapsable) {
            itemData.of.setAttribute('fx', x)
            itemData.of.setAttribute('fy', y)
          }
        }
        // gfill.setAttribute('fy','200');
      }
    }

    function renderStroke(styleData, itemData, isFirstFrame) {
      var styleElem = itemData.style
      var d = itemData.d
      if (d && (d._mdf || isFirstFrame) && d.dashStr) {
        styleElem.pElem.setAttribute('stroke-dasharray', d.dashStr)
        styleElem.pElem.setAttribute('stroke-dashoffset', d.dashoffset[0])
      }
      if (itemData.c && (itemData.c._mdf || isFirstFrame)) {
        styleElem.pElem.setAttribute(
          'stroke',
          'rgb(' + bmFloor(itemData.c.v[0]) + ',' + bmFloor(itemData.c.v[1]) + ',' + bmFloor(itemData.c.v[2]) + ')',
        )
      }
      if (itemData.o._mdf || isFirstFrame) {
        styleElem.pElem.setAttribute('stroke-opacity', itemData.o.v)
      }
      if (itemData.w._mdf || isFirstFrame) {
        styleElem.pElem.setAttribute('stroke-width', itemData.w.v)
        if (styleElem.msElem) {
          styleElem.msElem.setAttribute('stroke-width', itemData.w.v)
        }
      }
    }

    return ob
  })()

  function SVGShapeElement(data, globalData, comp) {
    // List of drawable elements
    this.shapes = []
    // Full shape data
    this.shapesData = data.shapes
    // List of styles that will be applied to shapes
    this.stylesList = []
    // List of modifiers that will be applied to shapes
    this.shapeModifiers = []
    // List of items in shape tree
    this.itemsData = []
    // List of items in previous shape tree
    this.processedElements = []
    // List of animated components
    this.animatedContents = []
    this.initElement(data, globalData, comp)
    // Moving any property that doesn't get too much access after initialization because of v8 way of handling more than 10 properties.
    // List of elements that have been created
    this.prevViewData = []
    // Moving any property that doesn't get too much access after initialization because of v8 way of handling more than 10 properties.
  }

  extendPrototype(
    [
      BaseElement,
      TransformElement,
      SVGBaseElement,
      IShapeElement,
      HierarchyElement,
      FrameElement,
      RenderableDOMElement,
    ],
    SVGShapeElement,
  )

  SVGShapeElement.prototype.initSecondaryElement = function () {}

  SVGShapeElement.prototype.identityMatrix = new Matrix()

  SVGShapeElement.prototype.buildExpressionInterface = function () {}

  SVGShapeElement.prototype.createContent = function () {
    this.searchShapes(this.shapesData, this.itemsData, this.prevViewData, this.layerElement, 0, [], true)
    this.filterUniqueShapes()
  }

  /*
This method searches for multiple shapes that affect a single element and one of them is animated
*/
  SVGShapeElement.prototype.filterUniqueShapes = function () {
    var i
    var len = this.shapes.length
    var shape
    var j
    var jLen = this.stylesList.length
    var style
    var tempShapes = []
    var areAnimated = false
    for (j = 0; j < jLen; j += 1) {
      style = this.stylesList[j]
      areAnimated = false
      tempShapes.length = 0
      for (i = 0; i < len; i += 1) {
        shape = this.shapes[i]
        if (shape.styles.indexOf(style) !== -1) {
          tempShapes.push(shape)
          areAnimated = shape._isAnimated || areAnimated
        }
      }
      if (tempShapes.length > 1 && areAnimated) {
        this.setShapesAsAnimated(tempShapes)
      }
    }
  }

  SVGShapeElement.prototype.setShapesAsAnimated = function (shapes) {
    var i
    var len = shapes.length
    for (i = 0; i < len; i += 1) {
      shapes[i].setAsAnimated()
    }
  }

  SVGShapeElement.prototype.createStyleElement = function (data, level) {
    // TODO: prevent drawing of hidden styles
    var elementData
    var styleOb = new SVGStyleData(data, level)

    var pathElement = styleOb.pElem
    if (data.ty === 'st') {
      elementData = new SVGStrokeStyleData(this, data, styleOb)
    } else if (data.ty === 'fl') {
      elementData = new SVGFillStyleData(this, data, styleOb)
    } else if (data.ty === 'gf' || data.ty === 'gs') {
      var GradientConstructor = data.ty === 'gf' ? SVGGradientFillStyleData : SVGGradientStrokeStyleData
      elementData = new GradientConstructor(this, data, styleOb)
      this.globalData.defs.appendChild(elementData.gf)
      if (elementData.maskId) {
        this.globalData.defs.appendChild(elementData.ms)
        this.globalData.defs.appendChild(elementData.of)
        pathElement.setAttribute('mask', 'url(' + getLocationHref() + '#' + elementData.maskId + ')')
      }
    } else if (data.ty === 'no') {
      elementData = new SVGNoStyleData(this, data, styleOb)
    }

    if (data.ty === 'st' || data.ty === 'gs') {
      pathElement.setAttribute('stroke-linecap', lineCapEnum[data.lc || 2])
      pathElement.setAttribute('stroke-linejoin', lineJoinEnum[data.lj || 2])
      pathElement.setAttribute('fill-opacity', '0')
      if (data.lj === 1) {
        pathElement.setAttribute('stroke-miterlimit', data.ml)
      }
    }

    if (data.r === 2) {
      pathElement.setAttribute('fill-rule', 'evenodd')
    }

    if (data.ln) {
      pathElement.setAttribute('id', data.ln)
    }
    if (data.cl) {
      pathElement.setAttribute('class', data.cl)
    }
    if (data.bm) {
      pathElement.style['mix-blend-mode'] = getBlendMode(data.bm)
    }
    this.stylesList.push(styleOb)
    this.addToAnimatedContents(data, elementData)
    return elementData
  }

  SVGShapeElement.prototype.createGroupElement = function (data) {
    var elementData = new ShapeGroupData()
    if (data.ln) {
      elementData.gr.setAttribute('id', data.ln)
    }
    if (data.cl) {
      elementData.gr.setAttribute('class', data.cl)
    }
    if (data.bm) {
      elementData.gr.style['mix-blend-mode'] = getBlendMode(data.bm)
    }
    return elementData
  }

  SVGShapeElement.prototype.createTransformElement = function (data, container) {
    var transformProperty = TransformPropertyFactory.getTransformProperty(this, data, this)
    var elementData = new SVGTransformData(transformProperty, transformProperty.o, container)
    this.addToAnimatedContents(data, elementData)
    return elementData
  }

  SVGShapeElement.prototype.createShapeElement = function (data, ownTransformers, level) {
    var ty = 4
    if (data.ty === 'rc') {
      ty = 5
    } else if (data.ty === 'el') {
      ty = 6
    } else if (data.ty === 'sr') {
      ty = 7
    }
    var shapeProperty = ShapePropertyFactory.getShapeProp(this, data, ty, this)
    var elementData = new SVGShapeData(ownTransformers, level, shapeProperty)
    this.shapes.push(elementData)
    this.addShapeToModifiers(elementData)
    this.addToAnimatedContents(data, elementData)
    return elementData
  }

  SVGShapeElement.prototype.addToAnimatedContents = function (data, element) {
    var i = 0
    var len = this.animatedContents.length
    while (i < len) {
      if (this.animatedContents[i].element === element) {
        return
      }
      i += 1
    }
    this.animatedContents.push({
      fn: SVGElementsRenderer.createRenderFunction(data),
      element: element,
      data: data,
    })
  }

  SVGShapeElement.prototype.setElementStyles = function (elementData) {
    var arr = elementData.styles
    var j
    var jLen = this.stylesList.length
    for (j = 0; j < jLen; j += 1) {
      if (!this.stylesList[j].closed) {
        arr.push(this.stylesList[j])
      }
    }
  }

  SVGShapeElement.prototype.reloadShapes = function () {
    this._isFirstFrame = true
    var i
    var len = this.itemsData.length
    for (i = 0; i < len; i += 1) {
      this.prevViewData[i] = this.itemsData[i]
    }
    this.searchShapes(this.shapesData, this.itemsData, this.prevViewData, this.layerElement, 0, [], true)
    this.filterUniqueShapes()
    len = this.dynamicProperties.length
    for (i = 0; i < len; i += 1) {
      this.dynamicProperties[i].getValue()
    }
    this.renderModifiers()
  }

  SVGShapeElement.prototype.searchShapes = function (
    arr,
    itemsData,
    prevViewData,
    container,
    level,
    transformers,
    render,
  ) {
    var ownTransformers = [].concat(transformers)
    var i
    var len = arr.length - 1
    var j
    var jLen
    var ownStyles = []
    var ownModifiers = []
    var currentTransform
    var modifier
    var processedPos
    for (i = len; i >= 0; i -= 1) {
      processedPos = this.searchProcessedElement(arr[i])
      if (!processedPos) {
        arr[i]._render = render
      } else {
        itemsData[i] = prevViewData[processedPos - 1]
      }
      if (arr[i].ty === 'fl' || arr[i].ty === 'st' || arr[i].ty === 'gf' || arr[i].ty === 'gs' || arr[i].ty === 'no') {
        if (!processedPos) {
          itemsData[i] = this.createStyleElement(arr[i], level)
        } else {
          itemsData[i].style.closed = false
        }
        if (arr[i]._render) {
          if (itemsData[i].style.pElem.parentNode !== container) {
            container.appendChild(itemsData[i].style.pElem)
          }
        }
        ownStyles.push(itemsData[i].style)
      } else if (arr[i].ty === 'gr') {
        if (!processedPos) {
          itemsData[i] = this.createGroupElement(arr[i])
        } else {
          jLen = itemsData[i].it.length
          for (j = 0; j < jLen; j += 1) {
            itemsData[i].prevViewData[j] = itemsData[i].it[j]
          }
        }
        this.searchShapes(
          arr[i].it,
          itemsData[i].it,
          itemsData[i].prevViewData,
          itemsData[i].gr,
          level + 1,
          ownTransformers,
          render,
        )
        if (arr[i]._render) {
          if (itemsData[i].gr.parentNode !== container) {
            container.appendChild(itemsData[i].gr)
          }
        }
      } else if (arr[i].ty === 'tr') {
        if (!processedPos) {
          itemsData[i] = this.createTransformElement(arr[i], container)
        }
        currentTransform = itemsData[i].transform
        ownTransformers.push(currentTransform)
      } else if (arr[i].ty === 'sh' || arr[i].ty === 'rc' || arr[i].ty === 'el' || arr[i].ty === 'sr') {
        if (!processedPos) {
          itemsData[i] = this.createShapeElement(arr[i], ownTransformers, level)
        }
        this.setElementStyles(itemsData[i])
      } else if (arr[i].ty === 'tm' || arr[i].ty === 'rd' || arr[i].ty === 'ms' || arr[i].ty === 'pb') {
        if (!processedPos) {
          modifier = ShapeModifiers.getModifier(arr[i].ty)
          modifier.init(this, arr[i])
          itemsData[i] = modifier
          this.shapeModifiers.push(modifier)
        } else {
          modifier = itemsData[i]
          modifier.closed = false
        }
        ownModifiers.push(modifier)
      } else if (arr[i].ty === 'rp') {
        if (!processedPos) {
          modifier = ShapeModifiers.getModifier(arr[i].ty)
          itemsData[i] = modifier
          modifier.init(this, arr, i, itemsData)
          this.shapeModifiers.push(modifier)
          render = false
        } else {
          modifier = itemsData[i]
          modifier.closed = true
        }
        ownModifiers.push(modifier)
      }
      this.addProcessedElement(arr[i], i + 1)
    }
    len = ownStyles.length
    for (i = 0; i < len; i += 1) {
      ownStyles[i].closed = true
    }
    len = ownModifiers.length
    for (i = 0; i < len; i += 1) {
      ownModifiers[i].closed = true
    }
  }

  SVGShapeElement.prototype.renderInnerContent = function () {
    this.renderModifiers()
    var i
    var len = this.stylesList.length
    for (i = 0; i < len; i += 1) {
      this.stylesList[i].reset()
    }
    this.renderShape()
    for (i = 0; i < len; i += 1) {
      if (this.stylesList[i]._mdf || this._isFirstFrame) {
        if (this.stylesList[i].msElem) {
          this.stylesList[i].msElem.setAttribute('d', this.stylesList[i].d)
          // Adding M0 0 fixes same mask bug on all browsers
          this.stylesList[i].d = 'M0 0' + this.stylesList[i].d
        }
        this.stylesList[i].pElem.setAttribute('d', this.stylesList[i].d || 'M0 0')
      }
    }
  }

  SVGShapeElement.prototype.renderShape = function () {
    var i
    var len = this.animatedContents.length
    var animatedContent
    for (i = 0; i < len; i += 1) {
      animatedContent = this.animatedContents[i]
      if ((this._isFirstFrame || animatedContent.element._isAnimated) && animatedContent.data !== true) {
        animatedContent.fn(animatedContent.data, animatedContent.element, this._isFirstFrame)
      }
    }
  }

  SVGShapeElement.prototype.destroy = function () {
    this.destroyBaseElement()
    this.shapesData = null
    this.itemsData = null
  }

  function LetterProps(o, sw, sc, fc, m, p) {
    this.o = o
    this.sw = sw
    this.sc = sc
    this.fc = fc
    this.m = m
    this.p = p
    this._mdf = {
      o: true,
      sw: !!sw,
      sc: !!sc,
      fc: !!fc,
      m: true,
      p: true,
    }
  }

  LetterProps.prototype.update = function (o, sw, sc, fc, m, p) {
    this._mdf.o = false
    this._mdf.sw = false
    this._mdf.sc = false
    this._mdf.fc = false
    this._mdf.m = false
    this._mdf.p = false
    var updated = false

    if (this.o !== o) {
      this.o = o
      this._mdf.o = true
      updated = true
    }
    if (this.sw !== sw) {
      this.sw = sw
      this._mdf.sw = true
      updated = true
    }
    if (this.sc !== sc) {
      this.sc = sc
      this._mdf.sc = true
      updated = true
    }
    if (this.fc !== fc) {
      this.fc = fc
      this._mdf.fc = true
      updated = true
    }
    if (this.m !== m) {
      this.m = m
      this._mdf.m = true
      updated = true
    }
    if (
      p.length &&
      (this.p[0] !== p[0] ||
        this.p[1] !== p[1] ||
        this.p[4] !== p[4] ||
        this.p[5] !== p[5] ||
        this.p[12] !== p[12] ||
        this.p[13] !== p[13])
    ) {
      this.p = p
      this._mdf.p = true
      updated = true
    }
    return updated
  }

  function TextProperty(elem, data) {
    this._frameId = initialDefaultFrame
    this.pv = ''
    this.v = ''
    this.kf = false
    this._isFirstFrame = true
    this._mdf = false
    this.data = data
    this.elem = elem
    this.comp = this.elem.comp
    this.keysIndex = 0
    this.canResize = false
    this.minimumFontSize = 1
    this.effectsSequence = []
    this.currentData = {
      ascent: 0,
      boxWidth: this.defaultBoxWidth,
      f: '',
      fStyle: '',
      fWeight: '',
      fc: '',
      j: '',
      justifyOffset: '',
      l: [],
      lh: 0,
      lineWidths: [],
      ls: '',
      of: '',
      s: '',
      sc: '',
      sw: 0,
      t: 0,
      tr: 0,
      sz: 0,
      ps: null,
      fillColorAnim: false,
      strokeColorAnim: false,
      strokeWidthAnim: false,
      yOffset: 0,
      finalSize: 0,
      finalText: [],
      finalLineHeight: 0,
      __complete: false,
    }
    this.copyData(this.currentData, this.data.d.k[0].s)

    if (!this.searchProperty()) {
      this.completeTextData(this.currentData)
    }
  }

  TextProperty.prototype.defaultBoxWidth = [0, 0]

  TextProperty.prototype.copyData = function (obj, data) {
    for (var s in data) {
      if (Object.prototype.hasOwnProperty.call(data, s)) {
        obj[s] = data[s]
      }
    }
    return obj
  }

  TextProperty.prototype.setCurrentData = function (data) {
    if (!data.__complete) {
      this.completeTextData(data)
    }
    this.currentData = data
    this.currentData.boxWidth = this.currentData.boxWidth || this.defaultBoxWidth
    this._mdf = true
  }

  TextProperty.prototype.searchProperty = function () {
    return this.searchKeyframes()
  }

  TextProperty.prototype.searchKeyframes = function () {
    this.kf = this.data.d.k.length > 1
    if (this.kf) {
      this.addEffect(this.getKeyframeValue.bind(this))
    }
    return this.kf
  }

  TextProperty.prototype.addEffect = function (effectFunction) {
    this.effectsSequence.push(effectFunction)
    this.elem.addDynamicProperty(this)
  }

  TextProperty.prototype.getValue = function (_finalValue) {
    if ((this.elem.globalData.frameId === this.frameId || !this.effectsSequence.length) && !_finalValue) {
      return
    }
    this.currentData.t = this.data.d.k[this.keysIndex].s.t
    var currentValue = this.currentData
    var currentIndex = this.keysIndex
    if (this.lock) {
      this.setCurrentData(this.currentData)
      return
    }
    this.lock = true
    this._mdf = false
    var i
    var len = this.effectsSequence.length
    var finalValue = _finalValue || this.data.d.k[this.keysIndex].s
    for (i = 0; i < len; i += 1) {
      // Checking if index changed to prevent creating a new object every time the expression updates.
      if (currentIndex !== this.keysIndex) {
        finalValue = this.effectsSequence[i](finalValue, finalValue.t)
      } else {
        finalValue = this.effectsSequence[i](this.currentData, finalValue.t)
      }
    }
    if (currentValue !== finalValue) {
      this.setCurrentData(finalValue)
    }
    this.v = this.currentData
    this.pv = this.v
    this.lock = false
    this.frameId = this.elem.globalData.frameId
  }

  TextProperty.prototype.getKeyframeValue = function () {
    var textKeys = this.data.d.k
    var frameNum = this.elem.comp.renderedFrame
    var i = 0
    var len = textKeys.length
    while (i <= len - 1) {
      if (i === len - 1 || textKeys[i + 1].t > frameNum) {
        break
      }
      i += 1
    }
    if (this.keysIndex !== i) {
      this.keysIndex = i
    }
    return this.data.d.k[this.keysIndex].s
  }

  TextProperty.prototype.buildFinalText = function (text) {
    var charactersArray = []
    var i = 0
    var len = text.length
    var charCode
    var secondCharCode
    var shouldCombine = false
    while (i < len) {
      charCode = text.charCodeAt(i)
      if (FontManager.isCombinedCharacter(charCode)) {
        charactersArray[charactersArray.length - 1] += text.charAt(i)
      } else if (charCode >= 0xd800 && charCode <= 0xdbff) {
        secondCharCode = text.charCodeAt(i + 1)
        if (secondCharCode >= 0xdc00 && secondCharCode <= 0xdfff) {
          if (shouldCombine || FontManager.isModifier(charCode, secondCharCode)) {
            charactersArray[charactersArray.length - 1] += text.substr(i, 2)
            shouldCombine = false
          } else {
            charactersArray.push(text.substr(i, 2))
          }
          i += 1
        } else {
          charactersArray.push(text.charAt(i))
        }
      } else if (charCode > 0xdbff) {
        secondCharCode = text.charCodeAt(i + 1)
        if (FontManager.isZeroWidthJoiner(charCode, secondCharCode)) {
          shouldCombine = true
          charactersArray[charactersArray.length - 1] += text.substr(i, 2)
          i += 1
        } else {
          charactersArray.push(text.charAt(i))
        }
      } else if (FontManager.isZeroWidthJoiner(charCode)) {
        charactersArray[charactersArray.length - 1] += text.charAt(i)
        shouldCombine = true
      } else {
        charactersArray.push(text.charAt(i))
      }
      i += 1
    }
    return charactersArray
  }

  TextProperty.prototype.completeTextData = function (documentData) {
    documentData.__complete = true
    var fontManager = this.elem.globalData.fontManager
    var data = this.data
    var letters = []
    var i
    var len
    var newLineFlag
    var index = 0
    var val
    var anchorGrouping = data.m.g
    var currentSize = 0
    var currentPos = 0
    var currentLine = 0
    var lineWidths = []
    var lineWidth = 0
    var maxLineWidth = 0
    var j
    var jLen
    var fontData = fontManager.getFontByName(documentData.f)
    var charData
    var cLength = 0

    var fontProps = getFontProperties(fontData)
    documentData.fWeight = fontProps.weight
    documentData.fStyle = fontProps.style
    documentData.finalSize = documentData.s
    documentData.finalText = this.buildFinalText(documentData.t)
    len = documentData.finalText.length
    documentData.finalLineHeight = documentData.lh
    var trackingOffset = (documentData.tr / 1000) * documentData.finalSize
    var charCode
    if (documentData.sz) {
      var flag = true
      var boxWidth = documentData.sz[0]
      var boxHeight = documentData.sz[1]
      var currentHeight
      var finalText
      while (flag) {
        finalText = this.buildFinalText(documentData.t)
        currentHeight = 0
        lineWidth = 0
        len = finalText.length
        trackingOffset = (documentData.tr / 1000) * documentData.finalSize
        var lastSpaceIndex = -1
        for (i = 0; i < len; i += 1) {
          charCode = finalText[i].charCodeAt(0)
          newLineFlag = false
          if (finalText[i] === ' ') {
            lastSpaceIndex = i
          } else if (charCode === 13 || charCode === 3) {
            lineWidth = 0
            newLineFlag = true
            currentHeight += documentData.finalLineHeight || documentData.finalSize * 1.2
          }
          if (fontManager.chars) {
            charData = fontManager.getCharData(finalText[i], fontData.fStyle, fontData.fFamily)
            cLength = newLineFlag ? 0 : (charData.w * documentData.finalSize) / 100
          } else {
            // tCanvasHelper.font = documentData.s + 'px '+ fontData.fFamily;
            cLength = fontManager.measureText(finalText[i], documentData.f, documentData.finalSize)
          }
          if (lineWidth + cLength > boxWidth && finalText[i] !== ' ') {
            if (lastSpaceIndex === -1) {
              len += 1
            } else {
              i = lastSpaceIndex
            }
            currentHeight += documentData.finalLineHeight || documentData.finalSize * 1.2
            finalText.splice(i, lastSpaceIndex === i ? 1 : 0, '\r')
            // finalText = finalText.substr(0,i) + "\r" + finalText.substr(i === lastSpaceIndex ? i + 1 : i);
            lastSpaceIndex = -1
            lineWidth = 0
          } else {
            lineWidth += cLength
            lineWidth += trackingOffset
          }
        }
        currentHeight += (fontData.ascent * documentData.finalSize) / 100
        if (this.canResize && documentData.finalSize > this.minimumFontSize && boxHeight < currentHeight) {
          documentData.finalSize -= 1
          documentData.finalLineHeight = (documentData.finalSize * documentData.lh) / documentData.s
        } else {
          documentData.finalText = finalText
          len = documentData.finalText.length
          flag = false
        }
      }
    }
    lineWidth = -trackingOffset
    cLength = 0
    var uncollapsedSpaces = 0
    var currentChar
    for (i = 0; i < len; i += 1) {
      newLineFlag = false
      currentChar = documentData.finalText[i]
      charCode = currentChar.charCodeAt(0)
      if (charCode === 13 || charCode === 3) {
        uncollapsedSpaces = 0
        lineWidths.push(lineWidth)
        maxLineWidth = lineWidth > maxLineWidth ? lineWidth : maxLineWidth
        lineWidth = -2 * trackingOffset
        val = ''
        newLineFlag = true
        currentLine += 1
      } else {
        val = currentChar
      }
      if (fontManager.chars) {
        charData = fontManager.getCharData(
          currentChar,
          fontData.fStyle,
          fontManager.getFontByName(documentData.f).fFamily,
        )
        cLength = newLineFlag ? 0 : (charData.w * documentData.finalSize) / 100
      } else {
        // var charWidth = fontManager.measureText(val, documentData.f, documentData.finalSize);
        // tCanvasHelper.font = documentData.finalSize + 'px '+ fontManager.getFontByName(documentData.f).fFamily;
        cLength = fontManager.measureText(val, documentData.f, documentData.finalSize)
      }

      //
      if (currentChar === ' ') {
        uncollapsedSpaces += cLength + trackingOffset
      } else {
        lineWidth += cLength + trackingOffset + uncollapsedSpaces
        uncollapsedSpaces = 0
      }
      letters.push({
        l: cLength,
        an: cLength,
        add: currentSize,
        n: newLineFlag,
        anIndexes: [],
        val: val,
        line: currentLine,
        animatorJustifyOffset: 0,
      })
      if (anchorGrouping == 2) {
        // eslint-disable-line eqeqeq
        currentSize += cLength
        if (val === '' || val === ' ' || i === len - 1) {
          if (val === '' || val === ' ') {
            currentSize -= cLength
          }
          while (currentPos <= i) {
            letters[currentPos].an = currentSize
            letters[currentPos].ind = index
            letters[currentPos].extra = cLength
            currentPos += 1
          }
          index += 1
          currentSize = 0
        }
      } else if (anchorGrouping == 3) {
        // eslint-disable-line eqeqeq
        currentSize += cLength
        if (val === '' || i === len - 1) {
          if (val === '') {
            currentSize -= cLength
          }
          while (currentPos <= i) {
            letters[currentPos].an = currentSize
            letters[currentPos].ind = index
            letters[currentPos].extra = cLength
            currentPos += 1
          }
          currentSize = 0
          index += 1
        }
      } else {
        letters[index].ind = index
        letters[index].extra = 0
        index += 1
      }
    }
    documentData.l = letters
    maxLineWidth = lineWidth > maxLineWidth ? lineWidth : maxLineWidth
    lineWidths.push(lineWidth)
    if (documentData.sz) {
      documentData.boxWidth = documentData.sz[0]
      documentData.justifyOffset = 0
    } else {
      documentData.boxWidth = maxLineWidth
      switch (documentData.j) {
        case 1:
          documentData.justifyOffset = -documentData.boxWidth
          break
        case 2:
          documentData.justifyOffset = -documentData.boxWidth / 2
          break
        default:
          documentData.justifyOffset = 0
      }
    }
    documentData.lineWidths = lineWidths

    var animators = data.a
    var animatorData
    var letterData
    jLen = animators.length
    var based
    var ind
    var indexes = []
    for (j = 0; j < jLen; j += 1) {
      animatorData = animators[j]
      if (animatorData.a.sc) {
        documentData.strokeColorAnim = true
      }
      if (animatorData.a.sw) {
        documentData.strokeWidthAnim = true
      }
      if (animatorData.a.fc || animatorData.a.fh || animatorData.a.fs || animatorData.a.fb) {
        documentData.fillColorAnim = true
      }
      ind = 0
      based = animatorData.s.b
      for (i = 0; i < len; i += 1) {
        letterData = letters[i]
        letterData.anIndexes[j] = ind
        if (
          (based == 1 && letterData.val !== '') ||
          (based == 2 && letterData.val !== '' && letterData.val !== ' ') ||
          (based == 3 && (letterData.n || letterData.val == ' ' || i == len - 1)) ||
          (based == 4 && (letterData.n || i == len - 1))
        ) {
          // eslint-disable-line eqeqeq
          if (animatorData.s.rn === 1) {
            indexes.push(ind)
          }
          ind += 1
        }
      }
      data.a[j].s.totalChars = ind
      var currentInd = -1
      var newInd
      if (animatorData.s.rn === 1) {
        for (i = 0; i < len; i += 1) {
          letterData = letters[i]
          if (currentInd != letterData.anIndexes[j]) {
            // eslint-disable-line eqeqeq
            currentInd = letterData.anIndexes[j]
            newInd = indexes.splice(Math.floor(Math.random() * indexes.length), 1)[0]
          }
          letterData.anIndexes[j] = newInd
        }
      }
    }
    documentData.yOffset = documentData.finalLineHeight || documentData.finalSize * 1.2
    documentData.ls = documentData.ls || 0
    documentData.ascent = (fontData.ascent * documentData.finalSize) / 100
  }

  TextProperty.prototype.updateDocumentData = function (newData, index) {
    index = index === undefined ? this.keysIndex : index
    var dData = this.copyData({}, this.data.d.k[index].s)
    dData = this.copyData(dData, newData)
    this.data.d.k[index].s = dData
    this.recalculate(index)
    this.elem.addDynamicProperty(this)
  }

  TextProperty.prototype.recalculate = function (index) {
    var dData = this.data.d.k[index].s
    dData.__complete = false
    this.keysIndex = 0
    this._isFirstFrame = true
    this.getValue(dData)
  }

  TextProperty.prototype.canResizeFont = function (_canResize) {
    this.canResize = _canResize
    this.recalculate(this.keysIndex)
    this.elem.addDynamicProperty(this)
  }

  TextProperty.prototype.setMinimumFontSize = function (_fontValue) {
    this.minimumFontSize = Math.floor(_fontValue) || 1
    this.recalculate(this.keysIndex)
    this.elem.addDynamicProperty(this)
  }

  const TextSelectorProp = (function () {
    var max = Math.max
    var min = Math.min
    var floor = Math.floor

    function TextSelectorPropFactory(elem, data) {
      this._currentTextLength = -1
      this.k = false
      this.data = data
      this.elem = elem
      this.comp = elem.comp
      this.finalS = 0
      this.finalE = 0
      this.initDynamicPropertyContainer(elem)
      this.s = PropertyFactory.getProp(elem, data.s || { k: 0 }, 0, 0, this)
      if ('e' in data) {
        this.e = PropertyFactory.getProp(elem, data.e, 0, 0, this)
      } else {
        this.e = { v: 100 }
      }
      this.o = PropertyFactory.getProp(elem, data.o || { k: 0 }, 0, 0, this)
      this.xe = PropertyFactory.getProp(elem, data.xe || { k: 0 }, 0, 0, this)
      this.ne = PropertyFactory.getProp(elem, data.ne || { k: 0 }, 0, 0, this)
      this.sm = PropertyFactory.getProp(elem, data.sm || { k: 100 }, 0, 0, this)
      this.a = PropertyFactory.getProp(elem, data.a, 0, 0.01, this)
      if (!this.dynamicProperties.length) {
        this.getValue()
      }
    }

    TextSelectorPropFactory.prototype = {
      getMult: function (ind) {
        if (this._currentTextLength !== this.elem.textProperty.currentData.l.length) {
          this.getValue()
        }
        var x1 = 0
        var y1 = 0
        var x2 = 1
        var y2 = 1
        if (this.ne.v > 0) {
          x1 = this.ne.v / 100.0
        } else {
          y1 = -this.ne.v / 100.0
        }
        if (this.xe.v > 0) {
          x2 = 1.0 - this.xe.v / 100.0
        } else {
          y2 = 1.0 + this.xe.v / 100.0
        }
        var easer = BezierFactory.getBezierEasing(x1, y1, x2, y2).get

        var mult = 0
        var s = this.finalS
        var e = this.finalE
        var type = this.data.sh
        if (type === 2) {
          if (e === s) {
            mult = ind >= e ? 1 : 0
          } else {
            mult = max(0, min(0.5 / (e - s) + (ind - s) / (e - s), 1))
          }
          mult = easer(mult)
        } else if (type === 3) {
          if (e === s) {
            mult = ind >= e ? 0 : 1
          } else {
            mult = 1 - max(0, min(0.5 / (e - s) + (ind - s) / (e - s), 1))
          }

          mult = easer(mult)
        } else if (type === 4) {
          if (e === s) {
            mult = 0
          } else {
            mult = max(0, min(0.5 / (e - s) + (ind - s) / (e - s), 1))
            if (mult < 0.5) {
              mult *= 2
            } else {
              mult = 1 - 2 * (mult - 0.5)
            }
          }
          mult = easer(mult)
        } else if (type === 5) {
          if (e === s) {
            mult = 0
          } else {
            var tot = e - s
            /* ind += 0.5;
                    mult = -4/(tot*tot)*(ind*ind)+(4/tot)*ind; */
            ind = min(max(0, ind + 0.5 - s), e - s)
            var x = -tot / 2 + ind
            var a = tot / 2
            mult = Math.sqrt(1 - (x * x) / (a * a))
          }
          mult = easer(mult)
        } else if (type === 6) {
          if (e === s) {
            mult = 0
          } else {
            ind = min(max(0, ind + 0.5 - s), e - s)
            mult = (1 + Math.cos(Math.PI + (Math.PI * 2 * ind) / (e - s))) / 2 // eslint-disable-line
          }
          mult = easer(mult)
        } else {
          if (ind >= floor(s)) {
            if (ind - s < 0) {
              mult = max(0, min(min(e, 1) - (s - ind), 1))
            } else {
              mult = max(0, min(e - ind, 1))
            }
          }
          mult = easer(mult)
        }
        // Smoothness implementation.
        // The smoothness represents a reduced range of the original [0; 1] range.
        // if smoothness is 25%, the new range will be [0.375; 0.625]
        // Steps are:
        // - find the lower value of the new range (threshold)
        // - if multiplier is smaller than that value, floor it to 0
        // - if it is larger,
        //     - subtract the threshold
        //     - divide it by the smoothness (this will return the range to [0; 1])
        // Note: If it doesn't work on some scenarios, consider applying it before the easer.
        if (this.sm.v !== 100) {
          var smoothness = this.sm.v * 0.01
          if (smoothness === 0) {
            smoothness = 0.00000001
          }
          var threshold = 0.5 - smoothness * 0.5
          if (mult < threshold) {
            mult = 0
          } else {
            mult = (mult - threshold) / smoothness
            if (mult > 1) {
              mult = 1
            }
          }
        }
        return mult * this.a.v
      },
      getValue: function (newCharsFlag) {
        this.iterateDynamicProperties()
        this._mdf = newCharsFlag || this._mdf
        this._currentTextLength = this.elem.textProperty.currentData.l.length || 0
        if (newCharsFlag && this.data.r === 2) {
          this.e.v = this._currentTextLength
        }
        var divisor = this.data.r === 2 ? 1 : 100 / this.data.totalChars
        var o = this.o.v / divisor
        var s = this.s.v / divisor + o
        var e = this.e.v / divisor + o
        if (s > e) {
          var _s = s
          s = e
          e = _s
        }
        this.finalS = s
        this.finalE = e
      },
    }
    extendPrototype([DynamicPropertyContainer], TextSelectorPropFactory)

    function getTextSelectorProp(elem, data, arr) {
      return new TextSelectorPropFactory(elem, data, arr)
    }

    return {
      getTextSelectorProp: getTextSelectorProp,
    }
  })()

  function TextAnimatorDataProperty(elem, animatorProps, container) {
    var defaultData = { propType: false }
    var getProp = PropertyFactory.getProp
    var textAnimatorAnimatables = animatorProps.a
    this.a = {
      r: textAnimatorAnimatables.r ? getProp(elem, textAnimatorAnimatables.r, 0, degToRads, container) : defaultData,
      rx: textAnimatorAnimatables.rx ? getProp(elem, textAnimatorAnimatables.rx, 0, degToRads, container) : defaultData,
      ry: textAnimatorAnimatables.ry ? getProp(elem, textAnimatorAnimatables.ry, 0, degToRads, container) : defaultData,
      sk: textAnimatorAnimatables.sk ? getProp(elem, textAnimatorAnimatables.sk, 0, degToRads, container) : defaultData,
      sa: textAnimatorAnimatables.sa ? getProp(elem, textAnimatorAnimatables.sa, 0, degToRads, container) : defaultData,
      s: textAnimatorAnimatables.s ? getProp(elem, textAnimatorAnimatables.s, 1, 0.01, container) : defaultData,
      a: textAnimatorAnimatables.a ? getProp(elem, textAnimatorAnimatables.a, 1, 0, container) : defaultData,
      o: textAnimatorAnimatables.o ? getProp(elem, textAnimatorAnimatables.o, 0, 0.01, container) : defaultData,
      p: textAnimatorAnimatables.p ? getProp(elem, textAnimatorAnimatables.p, 1, 0, container) : defaultData,
      sw: textAnimatorAnimatables.sw ? getProp(elem, textAnimatorAnimatables.sw, 0, 0, container) : defaultData,
      sc: textAnimatorAnimatables.sc ? getProp(elem, textAnimatorAnimatables.sc, 1, 0, container) : defaultData,
      fc: textAnimatorAnimatables.fc ? getProp(elem, textAnimatorAnimatables.fc, 1, 0, container) : defaultData,
      fh: textAnimatorAnimatables.fh ? getProp(elem, textAnimatorAnimatables.fh, 0, 0, container) : defaultData,
      fs: textAnimatorAnimatables.fs ? getProp(elem, textAnimatorAnimatables.fs, 0, 0.01, container) : defaultData,
      fb: textAnimatorAnimatables.fb ? getProp(elem, textAnimatorAnimatables.fb, 0, 0.01, container) : defaultData,
      t: textAnimatorAnimatables.t ? getProp(elem, textAnimatorAnimatables.t, 0, 0, container) : defaultData,
    }

    this.s = TextSelectorProp.getTextSelectorProp(elem, animatorProps.s, container)
    this.s.t = animatorProps.s.t
  }

  function TextAnimatorProperty(textData, renderType, elem) {
    this._isFirstFrame = true
    this._hasMaskedPath = false
    this._frameId = -1
    this._textData = textData
    this._renderType = renderType
    this._elem = elem
    this._animatorsData = createSizedArray(this._textData.a.length)
    this._pathData = {}
    this._moreOptions = {
      alignment: {},
    }
    this.renderedLetters = []
    this.lettersChangedFlag = false
    this.initDynamicPropertyContainer(elem)
  }

  TextAnimatorProperty.prototype.searchProperties = function () {
    var i
    var len = this._textData.a.length
    var animatorProps
    var getProp = PropertyFactory.getProp
    for (i = 0; i < len; i += 1) {
      animatorProps = this._textData.a[i]
      this._animatorsData[i] = new TextAnimatorDataProperty(this._elem, animatorProps, this)
    }
    if (this._textData.p && 'm' in this._textData.p) {
      this._pathData = {
        a: getProp(this._elem, this._textData.p.a, 0, 0, this),
        f: getProp(this._elem, this._textData.p.f, 0, 0, this),
        l: getProp(this._elem, this._textData.p.l, 0, 0, this),
        r: getProp(this._elem, this._textData.p.r, 0, 0, this),
        p: getProp(this._elem, this._textData.p.p, 0, 0, this),
        m: this._elem.maskManager.getMaskProperty(this._textData.p.m),
      }
      this._hasMaskedPath = true
    } else {
      this._hasMaskedPath = false
    }
    this._moreOptions.alignment = getProp(this._elem, this._textData.m.a, 1, 0, this)
  }

  TextAnimatorProperty.prototype.getMeasures = function (documentData, lettersChangedFlag) {
    this.lettersChangedFlag = lettersChangedFlag
    if (!this._mdf && !this._isFirstFrame && !lettersChangedFlag && (!this._hasMaskedPath || !this._pathData.m._mdf)) {
      return
    }
    this._isFirstFrame = false
    var alignment = this._moreOptions.alignment.v
    var animators = this._animatorsData
    var textData = this._textData
    var matrixHelper = this.mHelper
    var renderType = this._renderType
    var renderedLettersCount = this.renderedLetters.length
    var xPos
    var yPos
    var i
    var len
    var letters = documentData.l
    var pathInfo
    var currentLength
    var currentPoint
    var segmentLength
    var flag
    var pointInd
    var segmentInd
    var prevPoint
    var points
    var segments
    var partialLength
    var totalLength
    var perc
    var tanAngle
    var mask
    if (this._hasMaskedPath) {
      mask = this._pathData.m
      if (!this._pathData.n || this._pathData._mdf) {
        var paths = mask.v
        if (this._pathData.r.v) {
          paths = paths.reverse()
        }
        // TODO: release bezier data cached from previous pathInfo: this._pathData.pi
        pathInfo = {
          tLength: 0,
          segments: [],
        }
        len = paths._length - 1
        var bezierData
        totalLength = 0
        for (i = 0; i < len; i += 1) {
          bezierData = bez.buildBezierData(
            paths.v[i],
            paths.v[i + 1],
            [paths.o[i][0] - paths.v[i][0], paths.o[i][1] - paths.v[i][1]],
            [paths.i[i + 1][0] - paths.v[i + 1][0], paths.i[i + 1][1] - paths.v[i + 1][1]],
          )
          pathInfo.tLength += bezierData.segmentLength
          pathInfo.segments.push(bezierData)
          totalLength += bezierData.segmentLength
        }
        i = len
        if (mask.v.c) {
          bezierData = bez.buildBezierData(
            paths.v[i],
            paths.v[0],
            [paths.o[i][0] - paths.v[i][0], paths.o[i][1] - paths.v[i][1]],
            [paths.i[0][0] - paths.v[0][0], paths.i[0][1] - paths.v[0][1]],
          )
          pathInfo.tLength += bezierData.segmentLength
          pathInfo.segments.push(bezierData)
          totalLength += bezierData.segmentLength
        }
        this._pathData.pi = pathInfo
      }
      pathInfo = this._pathData.pi

      currentLength = this._pathData.f.v
      segmentInd = 0
      pointInd = 1
      segmentLength = 0
      flag = true
      segments = pathInfo.segments
      if (currentLength < 0 && mask.v.c) {
        if (pathInfo.tLength < Math.abs(currentLength)) {
          currentLength = -Math.abs(currentLength) % pathInfo.tLength
        }
        segmentInd = segments.length - 1
        points = segments[segmentInd].points
        pointInd = points.length - 1
        while (currentLength < 0) {
          currentLength += points[pointInd].partialLength
          pointInd -= 1
          if (pointInd < 0) {
            segmentInd -= 1
            points = segments[segmentInd].points
            pointInd = points.length - 1
          }
        }
      }
      points = segments[segmentInd].points
      prevPoint = points[pointInd - 1]
      currentPoint = points[pointInd]
      partialLength = currentPoint.partialLength
    }

    len = letters.length
    xPos = 0
    yPos = 0
    var yOff = documentData.finalSize * 1.2 * 0.714
    var firstLine = true
    var animatorProps
    var animatorSelector
    var j
    var jLen
    var letterValue

    jLen = animators.length

    var mult
    var ind = -1
    var offf
    var xPathPos
    var yPathPos
    var initPathPos = currentLength
    var initSegmentInd = segmentInd
    var initPointInd = pointInd
    var currentLine = -1
    var elemOpacity
    var sc
    var sw
    var fc
    var k
    var letterSw
    var letterSc
    var letterFc
    var letterM = ''
    var letterP = this.defaultPropsArray
    var letterO

    //
    if (documentData.j === 2 || documentData.j === 1) {
      var animatorJustifyOffset = 0
      var animatorFirstCharOffset = 0
      var justifyOffsetMult = documentData.j === 2 ? -0.5 : -1
      var lastIndex = 0
      var isNewLine = true

      for (i = 0; i < len; i += 1) {
        if (letters[i].n) {
          if (animatorJustifyOffset) {
            animatorJustifyOffset += animatorFirstCharOffset
          }
          while (lastIndex < i) {
            letters[lastIndex].animatorJustifyOffset = animatorJustifyOffset
            lastIndex += 1
          }
          animatorJustifyOffset = 0
          isNewLine = true
        } else {
          for (j = 0; j < jLen; j += 1) {
            animatorProps = animators[j].a
            if (animatorProps.t.propType) {
              if (isNewLine && documentData.j === 2) {
                animatorFirstCharOffset += animatorProps.t.v * justifyOffsetMult
              }
              animatorSelector = animators[j].s
              mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars)
              if (mult.length) {
                animatorJustifyOffset += animatorProps.t.v * mult[0] * justifyOffsetMult
              } else {
                animatorJustifyOffset += animatorProps.t.v * mult * justifyOffsetMult
              }
            }
          }
          isNewLine = false
        }
      }
      if (animatorJustifyOffset) {
        animatorJustifyOffset += animatorFirstCharOffset
      }
      while (lastIndex < i) {
        letters[lastIndex].animatorJustifyOffset = animatorJustifyOffset
        lastIndex += 1
      }
    }
    //

    for (i = 0; i < len; i += 1) {
      matrixHelper.reset()
      elemOpacity = 1
      if (letters[i].n) {
        xPos = 0
        yPos += documentData.yOffset
        yPos += firstLine ? 1 : 0
        currentLength = initPathPos
        firstLine = false
        if (this._hasMaskedPath) {
          segmentInd = initSegmentInd
          pointInd = initPointInd
          points = segments[segmentInd].points
          prevPoint = points[pointInd - 1]
          currentPoint = points[pointInd]
          partialLength = currentPoint.partialLength
          segmentLength = 0
        }
        letterM = ''
        letterFc = ''
        letterSw = ''
        letterO = ''
        letterP = this.defaultPropsArray
      } else {
        if (this._hasMaskedPath) {
          if (currentLine !== letters[i].line) {
            switch (documentData.j) {
              case 1:
                currentLength += totalLength - documentData.lineWidths[letters[i].line]
                break
              case 2:
                currentLength += (totalLength - documentData.lineWidths[letters[i].line]) / 2
                break
              default:
                break
            }
            currentLine = letters[i].line
          }
          if (ind !== letters[i].ind) {
            if (letters[ind]) {
              currentLength += letters[ind].extra
            }
            currentLength += letters[i].an / 2
            ind = letters[i].ind
          }
          currentLength += alignment[0] * letters[i].an * 0.005
          var animatorOffset = 0
          for (j = 0; j < jLen; j += 1) {
            animatorProps = animators[j].a
            if (animatorProps.p.propType) {
              animatorSelector = animators[j].s
              mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars)
              if (mult.length) {
                animatorOffset += animatorProps.p.v[0] * mult[0]
              } else {
                animatorOffset += animatorProps.p.v[0] * mult
              }
            }
            if (animatorProps.a.propType) {
              animatorSelector = animators[j].s
              mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars)
              if (mult.length) {
                animatorOffset += animatorProps.a.v[0] * mult[0]
              } else {
                animatorOffset += animatorProps.a.v[0] * mult
              }
            }
          }
          flag = true
          // Force alignment only works with a single line for now
          if (this._pathData.a.v) {
            currentLength =
              letters[0].an * 0.5 +
              ((totalLength - this._pathData.f.v - letters[0].an * 0.5 - letters[letters.length - 1].an * 0.5) * ind) /
                (len - 1)
            currentLength += this._pathData.f.v
          }
          while (flag) {
            if (segmentLength + partialLength >= currentLength + animatorOffset || !points) {
              perc = (currentLength + animatorOffset - segmentLength) / currentPoint.partialLength
              xPathPos = prevPoint.point[0] + (currentPoint.point[0] - prevPoint.point[0]) * perc
              yPathPos = prevPoint.point[1] + (currentPoint.point[1] - prevPoint.point[1]) * perc
              matrixHelper.translate(-alignment[0] * letters[i].an * 0.005, -(alignment[1] * yOff) * 0.01)
              flag = false
            } else if (points) {
              segmentLength += currentPoint.partialLength
              pointInd += 1
              if (pointInd >= points.length) {
                pointInd = 0
                segmentInd += 1
                if (!segments[segmentInd]) {
                  if (mask.v.c) {
                    pointInd = 0
                    segmentInd = 0
                    points = segments[segmentInd].points
                  } else {
                    segmentLength -= currentPoint.partialLength
                    points = null
                  }
                } else {
                  points = segments[segmentInd].points
                }
              }
              if (points) {
                prevPoint = currentPoint
                currentPoint = points[pointInd]
                partialLength = currentPoint.partialLength
              }
            }
          }
          offf = letters[i].an / 2 - letters[i].add
          matrixHelper.translate(-offf, 0, 0)
        } else {
          offf = letters[i].an / 2 - letters[i].add
          matrixHelper.translate(-offf, 0, 0)

          // Grouping alignment
          matrixHelper.translate(-alignment[0] * letters[i].an * 0.005, -alignment[1] * yOff * 0.01, 0)
        }

        for (j = 0; j < jLen; j += 1) {
          animatorProps = animators[j].a
          if (animatorProps.t.propType) {
            animatorSelector = animators[j].s
            mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars)
            // This condition is to prevent applying tracking to first character in each line. Might be better to use a boolean "isNewLine"
            if (xPos !== 0 || documentData.j !== 0) {
              if (this._hasMaskedPath) {
                if (mult.length) {
                  currentLength += animatorProps.t.v * mult[0]
                } else {
                  currentLength += animatorProps.t.v * mult
                }
              } else if (mult.length) {
                xPos += animatorProps.t.v * mult[0]
              } else {
                xPos += animatorProps.t.v * mult
              }
            }
          }
        }
        if (documentData.strokeWidthAnim) {
          sw = documentData.sw || 0
        }
        if (documentData.strokeColorAnim) {
          if (documentData.sc) {
            sc = [documentData.sc[0], documentData.sc[1], documentData.sc[2]]
          } else {
            sc = [0, 0, 0]
          }
        }
        if (documentData.fillColorAnim && documentData.fc) {
          fc = [documentData.fc[0], documentData.fc[1], documentData.fc[2]]
        }
        for (j = 0; j < jLen; j += 1) {
          animatorProps = animators[j].a
          if (animatorProps.a.propType) {
            animatorSelector = animators[j].s
            mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars)

            if (mult.length) {
              matrixHelper.translate(
                -animatorProps.a.v[0] * mult[0],
                -animatorProps.a.v[1] * mult[1],
                animatorProps.a.v[2] * mult[2],
              )
            } else {
              matrixHelper.translate(
                -animatorProps.a.v[0] * mult,
                -animatorProps.a.v[1] * mult,
                animatorProps.a.v[2] * mult,
              )
            }
          }
        }
        for (j = 0; j < jLen; j += 1) {
          animatorProps = animators[j].a
          if (animatorProps.s.propType) {
            animatorSelector = animators[j].s
            mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars)
            if (mult.length) {
              matrixHelper.scale(1 + (animatorProps.s.v[0] - 1) * mult[0], 1 + (animatorProps.s.v[1] - 1) * mult[1], 1)
            } else {
              matrixHelper.scale(1 + (animatorProps.s.v[0] - 1) * mult, 1 + (animatorProps.s.v[1] - 1) * mult, 1)
            }
          }
        }
        for (j = 0; j < jLen; j += 1) {
          animatorProps = animators[j].a
          animatorSelector = animators[j].s
          mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars)
          if (animatorProps.sk.propType) {
            if (mult.length) {
              matrixHelper.skewFromAxis(-animatorProps.sk.v * mult[0], animatorProps.sa.v * mult[1])
            } else {
              matrixHelper.skewFromAxis(-animatorProps.sk.v * mult, animatorProps.sa.v * mult)
            }
          }
          if (animatorProps.r.propType) {
            if (mult.length) {
              matrixHelper.rotateZ(-animatorProps.r.v * mult[2])
            } else {
              matrixHelper.rotateZ(-animatorProps.r.v * mult)
            }
          }
          if (animatorProps.ry.propType) {
            if (mult.length) {
              matrixHelper.rotateY(animatorProps.ry.v * mult[1])
            } else {
              matrixHelper.rotateY(animatorProps.ry.v * mult)
            }
          }
          if (animatorProps.rx.propType) {
            if (mult.length) {
              matrixHelper.rotateX(animatorProps.rx.v * mult[0])
            } else {
              matrixHelper.rotateX(animatorProps.rx.v * mult)
            }
          }
          if (animatorProps.o.propType) {
            if (mult.length) {
              elemOpacity += (animatorProps.o.v * mult[0] - elemOpacity) * mult[0]
            } else {
              elemOpacity += (animatorProps.o.v * mult - elemOpacity) * mult
            }
          }
          if (documentData.strokeWidthAnim && animatorProps.sw.propType) {
            if (mult.length) {
              sw += animatorProps.sw.v * mult[0]
            } else {
              sw += animatorProps.sw.v * mult
            }
          }
          if (documentData.strokeColorAnim && animatorProps.sc.propType) {
            for (k = 0; k < 3; k += 1) {
              if (mult.length) {
                sc[k] += (animatorProps.sc.v[k] - sc[k]) * mult[0]
              } else {
                sc[k] += (animatorProps.sc.v[k] - sc[k]) * mult
              }
            }
          }
          if (documentData.fillColorAnim && documentData.fc) {
            if (animatorProps.fc.propType) {
              for (k = 0; k < 3; k += 1) {
                if (mult.length) {
                  fc[k] += (animatorProps.fc.v[k] - fc[k]) * mult[0]
                } else {
                  fc[k] += (animatorProps.fc.v[k] - fc[k]) * mult
                }
              }
            }
            if (animatorProps.fh.propType) {
              if (mult.length) {
                fc = addHueToRGB(fc, animatorProps.fh.v * mult[0])
              } else {
                fc = addHueToRGB(fc, animatorProps.fh.v * mult)
              }
            }
            if (animatorProps.fs.propType) {
              if (mult.length) {
                fc = addSaturationToRGB(fc, animatorProps.fs.v * mult[0])
              } else {
                fc = addSaturationToRGB(fc, animatorProps.fs.v * mult)
              }
            }
            if (animatorProps.fb.propType) {
              if (mult.length) {
                fc = addBrightnessToRGB(fc, animatorProps.fb.v * mult[0])
              } else {
                fc = addBrightnessToRGB(fc, animatorProps.fb.v * mult)
              }
            }
          }
        }

        for (j = 0; j < jLen; j += 1) {
          animatorProps = animators[j].a

          if (animatorProps.p.propType) {
            animatorSelector = animators[j].s
            mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars)
            if (this._hasMaskedPath) {
              if (mult.length) {
                matrixHelper.translate(0, animatorProps.p.v[1] * mult[0], -animatorProps.p.v[2] * mult[1])
              } else {
                matrixHelper.translate(0, animatorProps.p.v[1] * mult, -animatorProps.p.v[2] * mult)
              }
            } else if (mult.length) {
              matrixHelper.translate(
                animatorProps.p.v[0] * mult[0],
                animatorProps.p.v[1] * mult[1],
                -animatorProps.p.v[2] * mult[2],
              )
            } else {
              matrixHelper.translate(
                animatorProps.p.v[0] * mult,
                animatorProps.p.v[1] * mult,
                -animatorProps.p.v[2] * mult,
              )
            }
          }
        }
        if (documentData.strokeWidthAnim) {
          letterSw = sw < 0 ? 0 : sw
        }
        if (documentData.strokeColorAnim) {
          letterSc =
            'rgb(' + Math.round(sc[0] * 255) + ',' + Math.round(sc[1] * 255) + ',' + Math.round(sc[2] * 255) + ')'
        }
        if (documentData.fillColorAnim && documentData.fc) {
          letterFc =
            'rgb(' + Math.round(fc[0] * 255) + ',' + Math.round(fc[1] * 255) + ',' + Math.round(fc[2] * 255) + ')'
        }

        if (this._hasMaskedPath) {
          matrixHelper.translate(0, -documentData.ls)

          matrixHelper.translate(0, alignment[1] * yOff * 0.01 + yPos, 0)
          if (this._pathData.p.v) {
            tanAngle = (currentPoint.point[1] - prevPoint.point[1]) / (currentPoint.point[0] - prevPoint.point[0])
            var rot = (Math.atan(tanAngle) * 180) / Math.PI
            if (currentPoint.point[0] < prevPoint.point[0]) {
              rot += 180
            }
            matrixHelper.rotate((-rot * Math.PI) / 180)
          }
          matrixHelper.translate(xPathPos, yPathPos, 0)
          currentLength -= alignment[0] * letters[i].an * 0.005
          if (letters[i + 1] && ind !== letters[i + 1].ind) {
            currentLength += letters[i].an / 2
            currentLength += documentData.tr * 0.001 * documentData.finalSize
          }
        } else {
          matrixHelper.translate(xPos, yPos, 0)

          if (documentData.ps) {
            // matrixHelper.translate(documentData.ps[0],documentData.ps[1],0);
            matrixHelper.translate(documentData.ps[0], documentData.ps[1] + documentData.ascent, 0)
          }
          switch (documentData.j) {
            case 1:
              matrixHelper.translate(
                letters[i].animatorJustifyOffset +
                  documentData.justifyOffset +
                  (documentData.boxWidth - documentData.lineWidths[letters[i].line]),
                0,
                0,
              )
              break
            case 2:
              matrixHelper.translate(
                letters[i].animatorJustifyOffset +
                  documentData.justifyOffset +
                  (documentData.boxWidth - documentData.lineWidths[letters[i].line]) / 2,
                0,
                0,
              )
              break
            default:
              break
          }
          matrixHelper.translate(0, -documentData.ls)
          matrixHelper.translate(offf, 0, 0)
          matrixHelper.translate(alignment[0] * letters[i].an * 0.005, alignment[1] * yOff * 0.01, 0)
          xPos += letters[i].l + documentData.tr * 0.001 * documentData.finalSize
        }
        if (renderType === 'html') {
          letterM = matrixHelper.toCSS()
        } else if (renderType === 'svg') {
          letterM = matrixHelper.to2dCSS()
        } else {
          letterP = [
            matrixHelper.props[0],
            matrixHelper.props[1],
            matrixHelper.props[2],
            matrixHelper.props[3],
            matrixHelper.props[4],
            matrixHelper.props[5],
            matrixHelper.props[6],
            matrixHelper.props[7],
            matrixHelper.props[8],
            matrixHelper.props[9],
            matrixHelper.props[10],
            matrixHelper.props[11],
            matrixHelper.props[12],
            matrixHelper.props[13],
            matrixHelper.props[14],
            matrixHelper.props[15],
          ]
        }
        letterO = elemOpacity
      }

      if (renderedLettersCount <= i) {
        letterValue = new LetterProps(letterO, letterSw, letterSc, letterFc, letterM, letterP)
        this.renderedLetters.push(letterValue)
        renderedLettersCount += 1
        this.lettersChangedFlag = true
      } else {
        letterValue = this.renderedLetters[i]
        this.lettersChangedFlag =
          letterValue.update(letterO, letterSw, letterSc, letterFc, letterM, letterP) || this.lettersChangedFlag
      }
    }
  }

  TextAnimatorProperty.prototype.getValue = function () {
    if (this._elem.globalData.frameId === this._frameId) {
      return
    }
    this._frameId = this._elem.globalData.frameId
    this.iterateDynamicProperties()
  }

  TextAnimatorProperty.prototype.mHelper = new Matrix()
  TextAnimatorProperty.prototype.defaultPropsArray = []
  extendPrototype([DynamicPropertyContainer], TextAnimatorProperty)

  function ITextElement() {}

  ITextElement.prototype.initElement = function (data, globalData, comp) {
    this.lettersChangedFlag = true
    this.initFrame()
    this.initBaseData(data, globalData, comp)
    this.textProperty = new TextProperty(this, data.t, this.dynamicProperties)
    this.textAnimator = new TextAnimatorProperty(data.t, this.renderType, this)
    this.initTransform(data, globalData, comp)
    this.initHierarchy()
    this.initRenderable()
    this.initRendererElement()
    this.createContainerElements()
    this.createRenderableComponents()
    this.createContent()
    this.hide()
    this.textAnimator.searchProperties(this.dynamicProperties)
  }

  ITextElement.prototype.prepareFrame = function (num) {
    this._mdf = false
    this.prepareRenderableFrame(num)
    this.prepareProperties(num, this.isInRange)
    if (this.textProperty._mdf || this.textProperty._isFirstFrame) {
      this.buildNewText()
      this.textProperty._isFirstFrame = false
      this.textProperty._mdf = false
    }
  }

  ITextElement.prototype.createPathShape = function (matrixHelper, shapes) {
    var j
    var jLen = shapes.length
    var pathNodes
    var shapeStr = ''
    for (j = 0; j < jLen; j += 1) {
      if (shapes[j].ty === 'sh') {
        pathNodes = shapes[j].ks.k
        shapeStr += buildShapeString(pathNodes, pathNodes.i.length, true, matrixHelper)
      }
    }
    return shapeStr
  }

  ITextElement.prototype.updateDocumentData = function (newData, index) {
    this.textProperty.updateDocumentData(newData, index)
  }

  ITextElement.prototype.canResizeFont = function (_canResize) {
    this.textProperty.canResizeFont(_canResize)
  }

  ITextElement.prototype.setMinimumFontSize = function (_fontSize) {
    this.textProperty.setMinimumFontSize(_fontSize)
  }

  ITextElement.prototype.applyTextPropertiesToMatrix = function (documentData, matrixHelper, lineNumber, xPos, yPos) {
    if (documentData.ps) {
      matrixHelper.translate(documentData.ps[0], documentData.ps[1] + documentData.ascent, 0)
    }
    matrixHelper.translate(0, -documentData.ls, 0)
    switch (documentData.j) {
      case 1:
        matrixHelper.translate(
          documentData.justifyOffset + (documentData.boxWidth - documentData.lineWidths[lineNumber]),
          0,
          0,
        )
        break
      case 2:
        matrixHelper.translate(
          documentData.justifyOffset + (documentData.boxWidth - documentData.lineWidths[lineNumber]) / 2,
          0,
          0,
        )
        break
      default:
        break
    }
    matrixHelper.translate(xPos, yPos, 0)
  }

  ITextElement.prototype.buildColor = function (colorData) {
    return (
      'rgb(' +
      Math.round(colorData[0] * 255) +
      ',' +
      Math.round(colorData[1] * 255) +
      ',' +
      Math.round(colorData[2] * 255) +
      ')'
    )
  }

  ITextElement.prototype.emptyProp = new LetterProps()

  ITextElement.prototype.destroy = function () {}

  var emptyShapeData = {
    shapes: [],
  }

  function SVGTextLottieElement(data, globalData, comp) {
    this.textSpans = []
    this.renderType = 'svg'
    this.initElement(data, globalData, comp)
  }

  extendPrototype(
    [BaseElement, TransformElement, SVGBaseElement, HierarchyElement, FrameElement, RenderableDOMElement, ITextElement],
    SVGTextLottieElement,
  )

  SVGTextLottieElement.prototype.createContent = function () {
    if (this.data.singleShape && !this.globalData.fontManager.chars) {
      this.textContainer = createNS('text')
    }
  }

  SVGTextLottieElement.prototype.buildTextContents = function (textArray) {
    var i = 0
    var len = textArray.length
    var textContents = []
    var currentTextContent = ''
    while (i < len) {
      if (textArray[i] === String.fromCharCode(13) || textArray[i] === String.fromCharCode(3)) {
        textContents.push(currentTextContent)
        currentTextContent = ''
      } else {
        currentTextContent += textArray[i]
      }
      i += 1
    }
    textContents.push(currentTextContent)
    return textContents
  }

  SVGTextLottieElement.prototype.buildShapeData = function (data, scale) {
    // data should probably be cloned to apply scale separately to each instance of a text on different layers
    // but since text internal content gets only rendered once and then it's never rerendered,
    // it's probably safe not to clone data and reuse always the same instance even if the object is mutated.
    // Avoiding cloning is preferred since cloning each character shape data is expensive
    if (data.shapes && data.shapes.length) {
      var shape = data.shapes[0]
      if (shape.it) {
        var shapeItem = shape.it[shape.it.length - 1]
        if (shapeItem.s) {
          shapeItem.s.k[0] = scale
          shapeItem.s.k[1] = scale
        }
      }
    }
    return data
  }

  SVGTextLottieElement.prototype.buildNewText = function () {
    this.addDynamicProperty(this)
    var i
    var len

    var documentData = this.textProperty.currentData
    this.renderedLetters = createSizedArray(documentData ? documentData.l.length : 0)
    if (documentData.fc) {
      this.layerElement.setAttribute('fill', this.buildColor(documentData.fc))
    } else {
      this.layerElement.setAttribute('fill', 'rgba(0,0,0,0)')
    }
    if (documentData.sc) {
      this.layerElement.setAttribute('stroke', this.buildColor(documentData.sc))
      this.layerElement.setAttribute('stroke-width', documentData.sw)
    }
    this.layerElement.setAttribute('font-size', documentData.finalSize)
    var fontData = this.globalData.fontManager.getFontByName(documentData.f)
    if (fontData.fClass) {
      this.layerElement.setAttribute('class', fontData.fClass)
    } else {
      this.layerElement.setAttribute('font-family', fontData.fFamily)
      var fWeight = documentData.fWeight
      var fStyle = documentData.fStyle
      this.layerElement.setAttribute('font-style', fStyle)
      this.layerElement.setAttribute('font-weight', fWeight)
    }
    this.layerElement.setAttribute('aria-label', documentData.t)

    var letters = documentData.l || []
    var usesGlyphs = !!this.globalData.fontManager.chars
    len = letters.length

    var tSpan
    var matrixHelper = this.mHelper
    var shapeStr = ''
    var singleShape = this.data.singleShape
    var xPos = 0
    var yPos = 0
    var firstLine = true
    var trackingOffset = documentData.tr * 0.001 * documentData.finalSize
    if (singleShape && !usesGlyphs && !documentData.sz) {
      var tElement = this.textContainer
      var justify = 'start'
      switch (documentData.j) {
        case 1:
          justify = 'end'
          break
        case 2:
          justify = 'middle'
          break
        default:
          justify = 'start'
          break
      }
      tElement.setAttribute('text-anchor', justify)
      tElement.setAttribute('letter-spacing', trackingOffset)
      var textContent = this.buildTextContents(documentData.finalText)
      len = textContent.length
      yPos = documentData.ps ? documentData.ps[1] + documentData.ascent : 0
      for (i = 0; i < len; i += 1) {
        tSpan = this.textSpans[i].span || createNS('tspan')
        tSpan.textContent = textContent[i]
        tSpan.setAttribute('x', 0)
        tSpan.setAttribute('y', yPos)
        tSpan.style.display = 'inherit'
        tElement.appendChild(tSpan)
        if (!this.textSpans[i]) {
          this.textSpans[i] = {
            span: null,
            glyph: null,
          }
        }
        this.textSpans[i].span = tSpan
        yPos += documentData.finalLineHeight
      }

      this.layerElement.appendChild(tElement)
    } else {
      var cachedSpansLength = this.textSpans.length
      var charData
      for (i = 0; i < len; i += 1) {
        if (!this.textSpans[i]) {
          this.textSpans[i] = {
            span: null,
            childSpan: null,
            glyph: null,
          }
        }
        if (!usesGlyphs || !singleShape || i === 0) {
          tSpan = cachedSpansLength > i ? this.textSpans[i].span : createNS(usesGlyphs ? 'g' : 'text')
          if (cachedSpansLength <= i) {
            tSpan.setAttribute('stroke-linecap', 'butt')
            tSpan.setAttribute('stroke-linejoin', 'round')
            tSpan.setAttribute('stroke-miterlimit', '4')
            this.textSpans[i].span = tSpan
            if (usesGlyphs) {
              var childSpan = createNS('g')
              tSpan.appendChild(childSpan)
              this.textSpans[i].childSpan = childSpan
            }
            this.textSpans[i].span = tSpan
            this.layerElement.appendChild(tSpan)
          }
          tSpan.style.display = 'inherit'
        }

        matrixHelper.reset()
        if (singleShape) {
          if (letters[i].n) {
            xPos = -trackingOffset
            yPos += documentData.yOffset
            yPos += firstLine ? 1 : 0
            firstLine = false
          }
          this.applyTextPropertiesToMatrix(documentData, matrixHelper, letters[i].line, xPos, yPos)
          xPos += letters[i].l || 0
          // xPos += letters[i].val === ' ' ? 0 : trackingOffset;
          xPos += trackingOffset
        }
        if (usesGlyphs) {
          charData = this.globalData.fontManager.getCharData(
            documentData.finalText[i],
            fontData.fStyle,
            this.globalData.fontManager.getFontByName(documentData.f).fFamily,
          )
          var glyphElement
          // t === 1 means the character has been replaced with an animated shaped
          if (charData.t === 1) {
            glyphElement = new SVGCompElement(charData.data, this.globalData, this)
          } else {
            var data = emptyShapeData
            if (charData.data && charData.data.shapes) {
              data = this.buildShapeData(charData.data, documentData.finalSize)
            }
            glyphElement = new SVGShapeElement(data, this.globalData, this)
          }
          if (this.textSpans[i].glyph) {
            var glyph = this.textSpans[i].glyph
            this.textSpans[i].childSpan.removeChild(glyph.layerElement)
            glyph.destroy()
          }
          this.textSpans[i].glyph = glyphElement
          glyphElement._debug = true
          glyphElement.prepareFrame(0)
          glyphElement.renderFrame()
          this.textSpans[i].childSpan.appendChild(glyphElement.layerElement)
          // when using animated shapes, the layer will be scaled instead of replacing the internal scale
          // this might have issues with strokes and might need a different solution
          if (charData.t === 1) {
            this.textSpans[i].childSpan.setAttribute(
              'transform',
              'scale(' + documentData.finalSize / 100 + ',' + documentData.finalSize / 100 + ')',
            )
          }
        } else {
          if (singleShape) {
            tSpan.setAttribute('transform', 'translate(' + matrixHelper.props[12] + ',' + matrixHelper.props[13] + ')')
          }
          tSpan.textContent = letters[i].val
          tSpan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve')
        }
        //
      }
      if (singleShape && tSpan) {
        tSpan.setAttribute('d', shapeStr)
      }
    }
    while (i < this.textSpans.length) {
      this.textSpans[i].span.style.display = 'none'
      i += 1
    }

    this._sizeChanged = true
  }

  SVGTextLottieElement.prototype.sourceRectAtTime = function () {
    this.prepareFrame(this.comp.renderedFrame - this.data.st)
    this.renderInnerContent()
    if (this._sizeChanged) {
      this._sizeChanged = false
      var textBox = this.layerElement.getBBox()
      this.bbox = {
        top: textBox.y,
        left: textBox.x,
        width: textBox.width,
        height: textBox.height,
      }
    }
    return this.bbox
  }

  SVGTextLottieElement.prototype.getValue = function () {
    var i
    var len = this.textSpans.length
    var glyphElement
    this.renderedFrame = this.comp.renderedFrame
    for (i = 0; i < len; i += 1) {
      glyphElement = this.textSpans[i].glyph
      if (glyphElement) {
        glyphElement.prepareFrame(this.comp.renderedFrame - this.data.st)
        if (glyphElement._mdf) {
          this._mdf = true
        }
      }
    }
  }

  SVGTextLottieElement.prototype.renderInnerContent = function () {
    if (!this.data.singleShape || this._mdf) {
      this.textAnimator.getMeasures(this.textProperty.currentData, this.lettersChangedFlag)
      if (this.lettersChangedFlag || this.textAnimator.lettersChangedFlag) {
        this._sizeChanged = true
        var i
        var len
        var renderedLetters = this.textAnimator.renderedLetters

        var letters = this.textProperty.currentData.l

        len = letters.length
        var renderedLetter
        var textSpan
        var glyphElement
        for (i = 0; i < len; i += 1) {
          if (!letters[i].n) {
            renderedLetter = renderedLetters[i]
            textSpan = this.textSpans[i].span
            glyphElement = this.textSpans[i].glyph
            if (glyphElement) {
              glyphElement.renderFrame()
            }
            if (renderedLetter._mdf.m) {
              textSpan.setAttribute('transform', renderedLetter.m)
            }
            if (renderedLetter._mdf.o) {
              textSpan.setAttribute('opacity', renderedLetter.o)
            }
            if (renderedLetter._mdf.sw) {
              textSpan.setAttribute('stroke-width', renderedLetter.sw)
            }
            if (renderedLetter._mdf.sc) {
              textSpan.setAttribute('stroke', renderedLetter.sc)
            }
            if (renderedLetter._mdf.fc) {
              textSpan.setAttribute('fill', renderedLetter.fc)
            }
          }
        }
      }
    }
  }

  function ISolidElement(data, globalData, comp) {
    this.initElement(data, globalData, comp)
  }
  extendPrototype([IImageElement], ISolidElement)

  ISolidElement.prototype.createContent = function () {
    var rect = createNS('rect')
    /// /rect.style.width = this.data.sw;
    /// /rect.style.height = this.data.sh;
    /// /rect.style.fill = this.data.sc;
    rect.setAttribute('width', this.data.sw)
    rect.setAttribute('height', this.data.sh)
    rect.setAttribute('fill', this.data.sc)
    this.layerElement.appendChild(rect)
  }

  function NullElement(data, globalData, comp) {
    this.initFrame()
    this.initBaseData(data, globalData, comp)
    this.initFrame()
    this.initTransform(data, globalData, comp)
    this.initHierarchy()
  }

  NullElement.prototype.prepareFrame = function (num) {
    this.prepareProperties(num, true)
  }

  NullElement.prototype.renderFrame = function () {}

  NullElement.prototype.getBaseElement = function () {
    return null
  }

  NullElement.prototype.destroy = function () {}

  NullElement.prototype.sourceRectAtTime = function () {}

  NullElement.prototype.hide = function () {}

  extendPrototype([BaseElement, TransformElement, HierarchyElement, FrameElement], NullElement)

  function SVGRendererBase() {}

  extendPrototype([BaseRenderer], SVGRendererBase)

  SVGRendererBase.prototype.createNull = function (data) {
    return new NullElement(data, this.globalData, this)
  }

  SVGRendererBase.prototype.createShape = function (data) {
    return new SVGShapeElement(data, this.globalData, this)
  }

  SVGRendererBase.prototype.createText = function (data) {
    return new SVGTextLottieElement(data, this.globalData, this)
  }

  SVGRendererBase.prototype.createImage = function (data) {
    return new IImageElement(data, this.globalData, this)
  }

  SVGRendererBase.prototype.createSolid = function (data) {
    return new ISolidElement(data, this.globalData, this)
  }

  SVGRendererBase.prototype.configAnimation = function (animData) {
    this.svgElement.setAttribute('xmlns', 'http://www.w3.org/2000/svg')
    if (this.renderConfig.viewBoxSize) {
      this.svgElement.setAttribute('viewBox', this.renderConfig.viewBoxSize)
    } else {
      this.svgElement.setAttribute('viewBox', '0 0 ' + animData.w + ' ' + animData.h)
    }

    if (!this.renderConfig.viewBoxOnly) {
      this.svgElement.setAttribute('width', animData.w)
      this.svgElement.setAttribute('height', animData.h)
      this.svgElement.style.width = '100%'
      this.svgElement.style.height = '100%'
      this.svgElement.style.transform = 'translate3d(0,0,0)'
      this.svgElement.style.contentVisibility = this.renderConfig.contentVisibility
    }
    if (this.renderConfig.width) {
      this.svgElement.setAttribute('width', this.renderConfig.width)
    }
    if (this.renderConfig.height) {
      this.svgElement.setAttribute('height', this.renderConfig.height)
    }
    if (this.renderConfig.className) {
      this.svgElement.setAttribute('class', this.renderConfig.className)
    }
    if (this.renderConfig.id) {
      this.svgElement.setAttribute('id', this.renderConfig.id)
    }
    if (this.renderConfig.focusable !== undefined) {
      this.svgElement.setAttribute('focusable', this.renderConfig.focusable)
    }
    this.svgElement.setAttribute('preserveAspectRatio', this.renderConfig.preserveAspectRatio)
    // this.layerElement.style.transform = 'translate3d(0,0,0)';
    // this.layerElement.style.transformOrigin = this.layerElement.style.mozTransformOrigin = this.layerElement.style.webkitTransformOrigin = this.layerElement.style['-webkit-transform'] = "0px 0px 0px";
    this.animationItem.wrapper.appendChild(this.svgElement)
    // Mask animation
    var defs = this.globalData.defs

    this.setupGlobalData(animData, defs)
    this.globalData.progressiveLoad = this.renderConfig.progressiveLoad
    this.data = animData

    var maskElement = createNS('clipPath')
    var rect = createNS('rect')
    rect.setAttribute('width', animData.w)
    rect.setAttribute('height', animData.h)
    rect.setAttribute('x', 0)
    rect.setAttribute('y', 0)
    var maskId = createElementID()
    maskElement.setAttribute('id', maskId)
    maskElement.appendChild(rect)
    this.layerElement.setAttribute('clip-path', 'url(' + getLocationHref() + '#' + maskId + ')')

    defs.appendChild(maskElement)
    this.layers = animData.layers
    this.elements = createSizedArray(animData.layers.length)
  }

  SVGRendererBase.prototype.destroy = function () {
    if (this.animationItem.wrapper) {
      this.animationItem.wrapper.innerText = ''
    }
    this.layerElement = null
    this.globalData.defs = null
    var i
    var len = this.layers ? this.layers.length : 0
    for (i = 0; i < len; i += 1) {
      if (this.elements[i]) {
        this.elements[i].destroy()
      }
    }
    this.elements.length = 0
    this.destroyed = true
    this.animationItem = null
  }

  SVGRendererBase.prototype.updateContainerSize = function () {}

  SVGRendererBase.prototype.buildItem = function (pos) {
    var elements = this.elements
    if (elements[pos] || this.layers[pos].ty === 99) {
      return
    }
    elements[pos] = true
    var element = this.createItem(this.layers[pos])

    elements[pos] = element
    if (getExpressionsPlugin()) {
      if (this.layers[pos].ty === 0) {
        this.globalData.projectInterface.registerComposition(element)
      }
      element.initExpressions()
    }
    this.appendElementInPos(element, pos)
    if (this.layers[pos].tt) {
      if (!this.elements[pos - 1] || this.elements[pos - 1] === true) {
        this.buildItem(pos - 1)
        this.addPendingElement(element)
      } else {
        element.setMatte(elements[pos - 1].layerId)
      }
    }
  }

  SVGRendererBase.prototype.checkPendingElements = function () {
    while (this.pendingElements.length) {
      var element = this.pendingElements.pop()
      element.checkParenting()
      if (element.data.tt) {
        var i = 0
        var len = this.elements.length
        while (i < len) {
          if (this.elements[i] === element) {
            element.setMatte(this.elements[i - 1].layerId)
            break
          }
          i += 1
        }
      }
    }
  }

  SVGRendererBase.prototype.renderFrame = function (num) {
    if (this.renderedFrame === num || this.destroyed) {
      return
    }
    if (num === null) {
      num = this.renderedFrame
    } else {
      this.renderedFrame = num
    }
    // console.log('-------');
    // console.log('FRAME ',num);
    this.globalData.frameNum = num
    this.globalData.frameId += 1
    this.globalData.projectInterface.currentFrame = num
    this.globalData._mdf = false
    var i
    var len = this.layers.length
    if (!this.completeLayers) {
      this.checkLayers(num)
    }
    for (i = len - 1; i >= 0; i -= 1) {
      if (this.completeLayers || this.elements[i]) {
        this.elements[i].prepareFrame(num - this.layers[i].st)
      }
    }
    if (this.globalData._mdf) {
      for (i = 0; i < len; i += 1) {
        if (this.completeLayers || this.elements[i]) {
          this.elements[i].renderFrame()
        }
      }
    }
  }

  SVGRendererBase.prototype.appendElementInPos = function (element, pos) {
    var newElement = element.getBaseElement()
    if (!newElement) {
      return
    }
    var i = 0
    var nextElement
    while (i < pos) {
      if (this.elements[i] && this.elements[i] !== true && this.elements[i].getBaseElement()) {
        nextElement = this.elements[i].getBaseElement()
      }
      i += 1
    }
    if (nextElement) {
      this.layerElement.insertBefore(newElement, nextElement)
    } else {
      this.layerElement.appendChild(newElement)
    }
  }

  SVGRendererBase.prototype.hide = function () {
    this.layerElement.style.display = 'none'
  }

  SVGRendererBase.prototype.show = function () {
    this.layerElement.style.display = 'block'
  }

  function ICompElement() {}

  extendPrototype([BaseElement, TransformElement, HierarchyElement, FrameElement, RenderableDOMElement], ICompElement)

  ICompElement.prototype.initElement = function (data, globalData, comp) {
    this.initFrame()
    this.initBaseData(data, globalData, comp)
    this.initTransform(data, globalData, comp)
    this.initRenderable()
    this.initHierarchy()
    this.initRendererElement()
    this.createContainerElements()
    this.createRenderableComponents()
    if (this.data.xt || !globalData.progressiveLoad) {
      this.buildAllItems()
    }
    this.hide()
  }

  /* ICompElement.prototype.hide = function(){
    if(!this.hidden){
        this.hideElement();
        var i,len = this.elements.length;
        for( i = 0; i < len; i+=1 ){
            if(this.elements[i]){
                this.elements[i].hide();
            }
        }
    }
}; */

  ICompElement.prototype.prepareFrame = function (num) {
    this._mdf = false
    this.prepareRenderableFrame(num)
    this.prepareProperties(num, this.isInRange)
    if (!this.isInRange && !this.data.xt) {
      return
    }

    if (!this.tm._placeholder) {
      var timeRemapped = this.tm.v
      if (timeRemapped === this.data.op) {
        timeRemapped = this.data.op - 1
      }
      this.renderedFrame = timeRemapped
    } else {
      this.renderedFrame = num / this.data.sr
    }
    var i
    var len = this.elements.length
    if (!this.completeLayers) {
      this.checkLayers(this.renderedFrame)
    }
    // This iteration needs to be backwards because of how expressions connect between each other
    for (i = len - 1; i >= 0; i -= 1) {
      if (this.completeLayers || this.elements[i]) {
        this.elements[i].prepareFrame(this.renderedFrame - this.layers[i].st)
        if (this.elements[i]._mdf) {
          this._mdf = true
        }
      }
    }
  }

  ICompElement.prototype.renderInnerContent = function () {
    var i
    var len = this.layers.length
    for (i = 0; i < len; i += 1) {
      if (this.completeLayers || this.elements[i]) {
        this.elements[i].renderFrame()
      }
    }
  }

  ICompElement.prototype.setElements = function (elems) {
    this.elements = elems
  }

  ICompElement.prototype.getElements = function () {
    return this.elements
  }

  ICompElement.prototype.destroyElements = function () {
    var i
    var len = this.layers.length
    for (i = 0; i < len; i += 1) {
      if (this.elements[i]) {
        this.elements[i].destroy()
      }
    }
  }

  ICompElement.prototype.destroy = function () {
    this.destroyElements()
    this.destroyBaseElement()
  }

  function SVGCompElement(data, globalData, comp) {
    this.layers = data.layers
    this.supports3d = true
    this.completeLayers = false
    this.pendingElements = []
    this.elements = this.layers ? createSizedArray(this.layers.length) : []
    this.initElement(data, globalData, comp)
    this.tm = data.tm ? PropertyFactory.getProp(this, data.tm, 0, globalData.frameRate, this) : { _placeholder: true }
  }

  extendPrototype([SVGRendererBase, ICompElement, SVGBaseElement], SVGCompElement)

  SVGCompElement.prototype.createComp = function (data) {
    return new SVGCompElement(data, this.globalData, this)
  }

  function SVGRenderer(animationItem, config) {
    this.animationItem = animationItem
    this.layers = null
    this.renderedFrame = -1
    this.svgElement = createNS('svg')
    var ariaLabel = ''
    if (config && config.title) {
      var titleElement = createNS('title')
      var titleId = createElementID()
      titleElement.setAttribute('id', titleId)
      titleElement.textContent = config.title
      this.svgElement.appendChild(titleElement)
      ariaLabel += titleId
    }
    if (config && config.description) {
      var descElement = createNS('desc')
      var descId = createElementID()
      descElement.setAttribute('id', descId)
      descElement.textContent = config.description
      this.svgElement.appendChild(descElement)
      ariaLabel += ' ' + descId
    }
    if (ariaLabel) {
      this.svgElement.setAttribute('aria-labelledby', ariaLabel)
    }
    var defs = createNS('defs')
    this.svgElement.appendChild(defs)
    var maskElement = createNS('g')
    this.svgElement.appendChild(maskElement)
    this.layerElement = maskElement
    this.renderConfig = {
      preserveAspectRatio: (config && config.preserveAspectRatio) || 'xMidYMid meet',
      imagePreserveAspectRatio: (config && config.imagePreserveAspectRatio) || 'xMidYMid slice',
      contentVisibility: (config && config.contentVisibility) || 'visible',
      progressiveLoad: (config && config.progressiveLoad) || false,
      hideOnTransparent: !(config && config.hideOnTransparent === false),
      viewBoxOnly: (config && config.viewBoxOnly) || false,
      viewBoxSize: (config && config.viewBoxSize) || false,
      className: (config && config.className) || '',
      id: (config && config.id) || '',
      focusable: config && config.focusable,
      filterSize: {
        width: (config && config.filterSize && config.filterSize.width) || '100%',
        height: (config && config.filterSize && config.filterSize.height) || '100%',
        x: (config && config.filterSize && config.filterSize.x) || '0%',
        y: (config && config.filterSize && config.filterSize.y) || '0%',
      },
      width: config && config.width,
      height: config && config.height,
    }

    this.globalData = {
      _mdf: false,
      frameNum: -1,
      defs: defs,
      renderConfig: this.renderConfig,
    }
    this.elements = []
    this.pendingElements = []
    this.destroyed = false
    this.rendererType = 'svg'
  }

  extendPrototype([SVGRendererBase], SVGRenderer)

  SVGRenderer.prototype.createComp = function (data) {
    return new SVGCompElement(data, this.globalData, this)
  }

  function CVContextData() {
    this.saved = []
    this.cArrPos = 0
    this.cTr = new Matrix()
    this.cO = 1
    var i
    var len = 15
    this.savedOp = createTypedArray('float32', len)
    for (i = 0; i < len; i += 1) {
      this.saved[i] = createTypedArray('float32', 16)
    }
    this._length = len
  }

  CVContextData.prototype.duplicate = function () {
    var newLength = this._length * 2
    var currentSavedOp = this.savedOp
    this.savedOp = createTypedArray('float32', newLength)
    this.savedOp.set(currentSavedOp)
    var i = 0
    for (i = this._length; i < newLength; i += 1) {
      this.saved[i] = createTypedArray('float32', 16)
    }
    this._length = newLength
  }

  CVContextData.prototype.reset = function () {
    this.cArrPos = 0
    this.cTr.reset()
    this.cO = 1
  }

  function ShapeTransformManager() {
    this.sequences = {}
    this.sequenceList = []
    this.transform_key_count = 0
  }

  ShapeTransformManager.prototype = {
    addTransformSequence: function (transforms) {
      var i
      var len = transforms.length
      var key = '_'
      for (i = 0; i < len; i += 1) {
        key += transforms[i].transform.key + '_'
      }
      var sequence = this.sequences[key]
      if (!sequence) {
        sequence = {
          transforms: [].concat(transforms),
          finalTransform: new Matrix(),
          _mdf: false,
        }
        this.sequences[key] = sequence
        this.sequenceList.push(sequence)
      }
      return sequence
    },
    processSequence: function (sequence, isFirstFrame) {
      var i = 0
      var len = sequence.transforms.length
      var _mdf = isFirstFrame
      while (i < len && !isFirstFrame) {
        if (sequence.transforms[i].transform.mProps._mdf) {
          _mdf = true
          break
        }
        i += 1
      }
      if (_mdf) {
        var props
        sequence.finalTransform.reset()
        for (i = len - 1; i >= 0; i -= 1) {
          props = sequence.transforms[i].transform.mProps.v.props
          sequence.finalTransform.transform(
            props[0],
            props[1],
            props[2],
            props[3],
            props[4],
            props[5],
            props[6],
            props[7],
            props[8],
            props[9],
            props[10],
            props[11],
            props[12],
            props[13],
            props[14],
            props[15],
          )
        }
      }
      sequence._mdf = _mdf
    },
    processSequences: function (isFirstFrame) {
      var i
      var len = this.sequenceList.length
      for (i = 0; i < len; i += 1) {
        this.processSequence(this.sequenceList[i], isFirstFrame)
      }
    },
    getNewKey: function () {
      this.transform_key_count += 1
      return '_' + this.transform_key_count
    },
  }

  function CVEffects() {}
  CVEffects.prototype.renderFrame = function () {}

  function CVMaskElement(data, element) {
    this.data = data
    this.element = element
    this.masksProperties = this.data.masksProperties || []
    this.viewData = createSizedArray(this.masksProperties.length)
    var i
    var len = this.masksProperties.length
    var hasMasks = false
    for (i = 0; i < len; i += 1) {
      if (this.masksProperties[i].mode !== 'n') {
        hasMasks = true
      }
      this.viewData[i] = ShapePropertyFactory.getShapeProp(this.element, this.masksProperties[i], 3)
    }
    this.hasMasks = hasMasks
    if (hasMasks) {
      this.element.addRenderableComponent(this)
    }
  }

  CVMaskElement.prototype.renderFrame = function () {
    if (!this.hasMasks) {
      return
    }
    var transform = this.element.finalTransform.mat
    var ctx = this.element.canvasContext
    var i
    var len = this.masksProperties.length
    var pt
    var pts
    var data
    ctx.beginPath()
    for (i = 0; i < len; i += 1) {
      if (this.masksProperties[i].mode !== 'n') {
        if (this.masksProperties[i].inv) {
          ctx.moveTo(0, 0)
          ctx.lineTo(this.element.globalData.compSize.w, 0)
          ctx.lineTo(this.element.globalData.compSize.w, this.element.globalData.compSize.h)
          ctx.lineTo(0, this.element.globalData.compSize.h)
          ctx.lineTo(0, 0)
        }
        data = this.viewData[i].v
        pt = transform.applyToPointArray(data.v[0][0], data.v[0][1], 0)
        ctx.moveTo(pt[0], pt[1])
        var j
        var jLen = data._length
        for (j = 1; j < jLen; j += 1) {
          pts = transform.applyToTriplePoints(data.o[j - 1], data.i[j], data.v[j])
          ctx.bezierCurveTo(pts[0], pts[1], pts[2], pts[3], pts[4], pts[5])
        }
        pts = transform.applyToTriplePoints(data.o[j - 1], data.i[0], data.v[0])
        ctx.bezierCurveTo(pts[0], pts[1], pts[2], pts[3], pts[4], pts[5])
      }
    }
    this.element.globalData.renderer.save(true)
    ctx.clip()
  }

  CVMaskElement.prototype.getMaskProperty = MaskElement.prototype.getMaskProperty

  CVMaskElement.prototype.destroy = function () {
    this.element = null
  }

  function CVBaseElement() {}

  CVBaseElement.prototype = {
    createElements: function () {},
    initRendererElement: function () {},
    createContainerElements: function () {
      this.canvasContext = this.globalData.canvasContext
      this.renderableEffectsManager = new CVEffects(this)
    },
    createContent: function () {},
    setBlendMode: function () {
      var globalData = this.globalData
      if (globalData.blendMode !== this.data.bm) {
        globalData.blendMode = this.data.bm
        var blendModeValue = getBlendMode(this.data.bm)
        globalData.canvasContext.globalCompositeOperation = blendModeValue
      }
    },
    createRenderableComponents: function () {
      this.maskManager = new CVMaskElement(this.data, this)
    },
    hideElement: function () {
      if (!this.hidden && (!this.isInRange || this.isTransparent)) {
        this.hidden = true
      }
    },
    showElement: function () {
      if (this.isInRange && !this.isTransparent) {
        this.hidden = false
        this._isFirstFrame = true
        this.maskManager._isFirstFrame = true
      }
    },
    renderFrame: function () {
      if (this.hidden || this.data.hd) {
        return
      }
      this.renderTransform()
      this.renderRenderable()
      this.setBlendMode()
      var forceRealStack = this.data.ty === 0
      this.globalData.renderer.save(forceRealStack)
      this.globalData.renderer.ctxTransform(this.finalTransform.mat.props)
      this.globalData.renderer.ctxOpacity(this.finalTransform.mProp.o.v)
      this.renderInnerContent()
      this.globalData.renderer.restore(forceRealStack)
      if (this.maskManager.hasMasks) {
        this.globalData.renderer.restore(true)
      }
      if (this._isFirstFrame) {
        this._isFirstFrame = false
      }
    },
    destroy: function () {
      this.canvasContext = null
      this.data = null
      this.globalData = null
      this.maskManager.destroy()
    },
    mHelper: new Matrix(),
  }
  CVBaseElement.prototype.hide = CVBaseElement.prototype.hideElement
  CVBaseElement.prototype.show = CVBaseElement.prototype.showElement

  function CVShapeData(element, data, styles, transformsManager) {
    this.styledShapes = []
    this.tr = [0, 0, 0, 0, 0, 0]
    var ty = 4
    if (data.ty === 'rc') {
      ty = 5
    } else if (data.ty === 'el') {
      ty = 6
    } else if (data.ty === 'sr') {
      ty = 7
    }
    this.sh = ShapePropertyFactory.getShapeProp(element, data, ty, element)
    var i
    var len = styles.length
    var styledShape
    for (i = 0; i < len; i += 1) {
      if (!styles[i].closed) {
        styledShape = {
          transforms: transformsManager.addTransformSequence(styles[i].transforms),
          trNodes: [],
        }
        this.styledShapes.push(styledShape)
        styles[i].elements.push(styledShape)
      }
    }
  }

  CVShapeData.prototype.setAsAnimated = SVGShapeData.prototype.setAsAnimated

  function CVShapeElement(data, globalData, comp) {
    this.shapes = []
    this.shapesData = data.shapes
    this.stylesList = []
    this.itemsData = []
    this.prevViewData = []
    this.shapeModifiers = []
    this.processedElements = []
    this.transformsManager = new ShapeTransformManager()
    this.initElement(data, globalData, comp)
  }

  extendPrototype(
    [BaseElement, TransformElement, CVBaseElement, IShapeElement, HierarchyElement, FrameElement, RenderableElement],
    CVShapeElement,
  )

  CVShapeElement.prototype.initElement = RenderableDOMElement.prototype.initElement

  CVShapeElement.prototype.transformHelper = { opacity: 1, _opMdf: false }

  CVShapeElement.prototype.dashResetter = []

  CVShapeElement.prototype.createContent = function () {
    this.searchShapes(this.shapesData, this.itemsData, this.prevViewData, true, [])
  }

  CVShapeElement.prototype.createStyleElement = function (data, transforms) {
    var styleElem = {
      data: data,
      type: data.ty,
      preTransforms: this.transformsManager.addTransformSequence(transforms),
      transforms: [],
      elements: [],
      closed: data.hd === true,
    }
    var elementData = {}
    if (data.ty === 'fl' || data.ty === 'st') {
      elementData.c = PropertyFactory.getProp(this, data.c, 1, 255, this)
      if (!elementData.c.k) {
        styleElem.co =
          'rgb(' +
          bmFloor(elementData.c.v[0]) +
          ',' +
          bmFloor(elementData.c.v[1]) +
          ',' +
          bmFloor(elementData.c.v[2]) +
          ')'
      }
    } else if (data.ty === 'gf' || data.ty === 'gs') {
      elementData.s = PropertyFactory.getProp(this, data.s, 1, null, this)
      elementData.e = PropertyFactory.getProp(this, data.e, 1, null, this)
      elementData.h = PropertyFactory.getProp(this, data.h || { k: 0 }, 0, 0.01, this)
      elementData.a = PropertyFactory.getProp(this, data.a || { k: 0 }, 0, degToRads, this)
      elementData.g = new GradientProperty(this, data.g, this)
    }
    elementData.o = PropertyFactory.getProp(this, data.o, 0, 0.01, this)
    if (data.ty === 'st' || data.ty === 'gs') {
      styleElem.lc = lineCapEnum[data.lc || 2]
      styleElem.lj = lineJoinEnum[data.lj || 2]
      if (data.lj == 1) {
        // eslint-disable-line eqeqeq
        styleElem.ml = data.ml
      }
      elementData.w = PropertyFactory.getProp(this, data.w, 0, null, this)
      if (!elementData.w.k) {
        styleElem.wi = elementData.w.v
      }
      if (data.d) {
        var d = new DashProperty(this, data.d, 'canvas', this)
        elementData.d = d
        if (!elementData.d.k) {
          styleElem.da = elementData.d.dashArray
          styleElem.do = elementData.d.dashoffset[0]
        }
      }
    } else {
      styleElem.r = data.r === 2 ? 'evenodd' : 'nonzero'
    }
    this.stylesList.push(styleElem)
    elementData.style = styleElem
    return elementData
  }

  CVShapeElement.prototype.createGroupElement = function () {
    var elementData = {
      it: [],
      prevViewData: [],
    }
    return elementData
  }

  CVShapeElement.prototype.createTransformElement = function (data) {
    var elementData = {
      transform: {
        opacity: 1,
        _opMdf: false,
        key: this.transformsManager.getNewKey(),
        op: PropertyFactory.getProp(this, data.o, 0, 0.01, this),
        mProps: TransformPropertyFactory.getTransformProperty(this, data, this),
      },
    }
    return elementData
  }

  CVShapeElement.prototype.createShapeElement = function (data) {
    var elementData = new CVShapeData(this, data, this.stylesList, this.transformsManager)

    this.shapes.push(elementData)
    this.addShapeToModifiers(elementData)
    return elementData
  }

  CVShapeElement.prototype.reloadShapes = function () {
    this._isFirstFrame = true
    var i
    var len = this.itemsData.length
    for (i = 0; i < len; i += 1) {
      this.prevViewData[i] = this.itemsData[i]
    }
    this.searchShapes(this.shapesData, this.itemsData, this.prevViewData, true, [])
    len = this.dynamicProperties.length
    for (i = 0; i < len; i += 1) {
      this.dynamicProperties[i].getValue()
    }
    this.renderModifiers()
    this.transformsManager.processSequences(this._isFirstFrame)
  }

  CVShapeElement.prototype.addTransformToStyleList = function (transform) {
    var i
    var len = this.stylesList.length
    for (i = 0; i < len; i += 1) {
      if (!this.stylesList[i].closed) {
        this.stylesList[i].transforms.push(transform)
      }
    }
  }

  CVShapeElement.prototype.removeTransformFromStyleList = function () {
    var i
    var len = this.stylesList.length
    for (i = 0; i < len; i += 1) {
      if (!this.stylesList[i].closed) {
        this.stylesList[i].transforms.pop()
      }
    }
  }

  CVShapeElement.prototype.closeStyles = function (styles) {
    var i
    var len = styles.length
    for (i = 0; i < len; i += 1) {
      styles[i].closed = true
    }
  }

  CVShapeElement.prototype.searchShapes = function (arr, itemsData, prevViewData, shouldRender, transforms) {
    var i
    var len = arr.length - 1
    var j
    var jLen
    var ownStyles = []
    var ownModifiers = []
    var processedPos
    var modifier
    var currentTransform
    var ownTransforms = [].concat(transforms)
    for (i = len; i >= 0; i -= 1) {
      processedPos = this.searchProcessedElement(arr[i])
      if (!processedPos) {
        arr[i]._shouldRender = shouldRender
      } else {
        itemsData[i] = prevViewData[processedPos - 1]
      }
      if (arr[i].ty === 'fl' || arr[i].ty === 'st' || arr[i].ty === 'gf' || arr[i].ty === 'gs') {
        if (!processedPos) {
          itemsData[i] = this.createStyleElement(arr[i], ownTransforms)
        } else {
          itemsData[i].style.closed = false
        }

        ownStyles.push(itemsData[i].style)
      } else if (arr[i].ty === 'gr') {
        if (!processedPos) {
          itemsData[i] = this.createGroupElement(arr[i])
        } else {
          jLen = itemsData[i].it.length
          for (j = 0; j < jLen; j += 1) {
            itemsData[i].prevViewData[j] = itemsData[i].it[j]
          }
        }
        this.searchShapes(arr[i].it, itemsData[i].it, itemsData[i].prevViewData, shouldRender, ownTransforms)
      } else if (arr[i].ty === 'tr') {
        if (!processedPos) {
          currentTransform = this.createTransformElement(arr[i])
          itemsData[i] = currentTransform
        }
        ownTransforms.push(itemsData[i])
        this.addTransformToStyleList(itemsData[i])
      } else if (arr[i].ty === 'sh' || arr[i].ty === 'rc' || arr[i].ty === 'el' || arr[i].ty === 'sr') {
        if (!processedPos) {
          itemsData[i] = this.createShapeElement(arr[i])
        }
      } else if (arr[i].ty === 'tm' || arr[i].ty === 'rd' || arr[i].ty === 'pb') {
        if (!processedPos) {
          modifier = ShapeModifiers.getModifier(arr[i].ty)
          modifier.init(this, arr[i])
          itemsData[i] = modifier
          this.shapeModifiers.push(modifier)
        } else {
          modifier = itemsData[i]
          modifier.closed = false
        }
        ownModifiers.push(modifier)
      } else if (arr[i].ty === 'rp') {
        if (!processedPos) {
          modifier = ShapeModifiers.getModifier(arr[i].ty)
          itemsData[i] = modifier
          modifier.init(this, arr, i, itemsData)
          this.shapeModifiers.push(modifier)
          shouldRender = false
        } else {
          modifier = itemsData[i]
          modifier.closed = true
        }
        ownModifiers.push(modifier)
      }
      this.addProcessedElement(arr[i], i + 1)
    }
    this.removeTransformFromStyleList()
    this.closeStyles(ownStyles)
    len = ownModifiers.length
    for (i = 0; i < len; i += 1) {
      ownModifiers[i].closed = true
    }
  }

  CVShapeElement.prototype.renderInnerContent = function () {
    this.transformHelper.opacity = 1
    this.transformHelper._opMdf = false
    this.renderModifiers()
    this.transformsManager.processSequences(this._isFirstFrame)
    this.renderShape(this.transformHelper, this.shapesData, this.itemsData, true)
  }

  CVShapeElement.prototype.renderShapeTransform = function (parentTransform, groupTransform) {
    if (parentTransform._opMdf || groupTransform.op._mdf || this._isFirstFrame) {
      groupTransform.opacity = parentTransform.opacity
      groupTransform.opacity *= groupTransform.op.v
      groupTransform._opMdf = true
    }
  }

  CVShapeElement.prototype.drawLayer = function () {
    var i
    var len = this.stylesList.length
    var j
    var jLen
    var k
    var kLen
    var elems
    var nodes
    var renderer = this.globalData.renderer
    var ctx = this.globalData.canvasContext
    var type
    var currentStyle
    for (i = 0; i < len; i += 1) {
      currentStyle = this.stylesList[i]
      type = currentStyle.type

      // Skipping style when
      // Stroke width equals 0
      // style should not be rendered (extra unused repeaters)
      // current opacity equals 0
      // global opacity equals 0
      if (
        !(
          ((type === 'st' || type === 'gs') && currentStyle.wi === 0) ||
          !currentStyle.data._shouldRender ||
          currentStyle.coOp === 0 ||
          this.globalData.currentGlobalAlpha === 0
        )
      ) {
        renderer.save()
        elems = currentStyle.elements
        if (type === 'st' || type === 'gs') {
          ctx.strokeStyle = type === 'st' ? currentStyle.co : currentStyle.grd
          ctx.lineWidth = currentStyle.wi
          ctx.lineCap = currentStyle.lc
          ctx.lineJoin = currentStyle.lj
          ctx.miterLimit = currentStyle.ml || 0
        } else {
          ctx.fillStyle = type === 'fl' ? currentStyle.co : currentStyle.grd
        }
        renderer.ctxOpacity(currentStyle.coOp)
        if (type !== 'st' && type !== 'gs') {
          ctx.beginPath()
        }
        renderer.ctxTransform(currentStyle.preTransforms.finalTransform.props)
        jLen = elems.length
        for (j = 0; j < jLen; j += 1) {
          if (type === 'st' || type === 'gs') {
            ctx.beginPath()
            if (currentStyle.da) {
              ctx.setLineDash(currentStyle.da)
              ctx.lineDashOffset = currentStyle.do
            }
          }
          nodes = elems[j].trNodes
          kLen = nodes.length

          for (k = 0; k < kLen; k += 1) {
            if (nodes[k].t === 'm') {
              ctx.moveTo(nodes[k].p[0], nodes[k].p[1])
            } else if (nodes[k].t === 'c') {
              ctx.bezierCurveTo(
                nodes[k].pts[0],
                nodes[k].pts[1],
                nodes[k].pts[2],
                nodes[k].pts[3],
                nodes[k].pts[4],
                nodes[k].pts[5],
              )
            } else {
              ctx.closePath()
            }
          }
          if (type === 'st' || type === 'gs') {
            ctx.stroke()
            if (currentStyle.da) {
              ctx.setLineDash(this.dashResetter)
            }
          }
        }
        if (type !== 'st' && type !== 'gs') {
          ctx.fill(currentStyle.r)
        }
        renderer.restore()
      }
    }
  }

  CVShapeElement.prototype.renderShape = function (parentTransform, items, data, isMain) {
    var i
    var len = items.length - 1
    var groupTransform
    groupTransform = parentTransform
    for (i = len; i >= 0; i -= 1) {
      if (items[i].ty === 'tr') {
        groupTransform = data[i].transform
        this.renderShapeTransform(parentTransform, groupTransform)
      } else if (items[i].ty === 'sh' || items[i].ty === 'el' || items[i].ty === 'rc' || items[i].ty === 'sr') {
        this.renderPath(items[i], data[i])
      } else if (items[i].ty === 'fl') {
        this.renderFill(items[i], data[i], groupTransform)
      } else if (items[i].ty === 'st') {
        this.renderStroke(items[i], data[i], groupTransform)
      } else if (items[i].ty === 'gf' || items[i].ty === 'gs') {
        this.renderGradientFill(items[i], data[i], groupTransform)
      } else if (items[i].ty === 'gr') {
        this.renderShape(groupTransform, items[i].it, data[i].it)
      } else if (items[i].ty === 'tm') {
        //
      }
    }
    if (isMain) {
      this.drawLayer()
    }
  }

  CVShapeElement.prototype.renderStyledShape = function (styledShape, shape) {
    if (this._isFirstFrame || shape._mdf || styledShape.transforms._mdf) {
      var shapeNodes = styledShape.trNodes
      var paths = shape.paths
      var i
      var len
      var j
      var jLen = paths._length
      shapeNodes.length = 0
      var groupTransformMat = styledShape.transforms.finalTransform
      for (j = 0; j < jLen; j += 1) {
        var pathNodes = paths.shapes[j]
        if (pathNodes && pathNodes.v) {
          len = pathNodes._length
          for (i = 1; i < len; i += 1) {
            if (i === 1) {
              shapeNodes.push({
                t: 'm',
                p: groupTransformMat.applyToPointArray(pathNodes.v[0][0], pathNodes.v[0][1], 0),
              })
            }
            shapeNodes.push({
              t: 'c',
              pts: groupTransformMat.applyToTriplePoints(pathNodes.o[i - 1], pathNodes.i[i], pathNodes.v[i]),
            })
          }
          if (len === 1) {
            shapeNodes.push({
              t: 'm',
              p: groupTransformMat.applyToPointArray(pathNodes.v[0][0], pathNodes.v[0][1], 0),
            })
          }
          if (pathNodes.c && len) {
            shapeNodes.push({
              t: 'c',
              pts: groupTransformMat.applyToTriplePoints(pathNodes.o[i - 1], pathNodes.i[0], pathNodes.v[0]),
            })
            shapeNodes.push({
              t: 'z',
            })
          }
        }
      }
      styledShape.trNodes = shapeNodes
    }
  }

  CVShapeElement.prototype.renderPath = function (pathData, itemData) {
    if (pathData.hd !== true && pathData._shouldRender) {
      var i
      var len = itemData.styledShapes.length
      for (i = 0; i < len; i += 1) {
        this.renderStyledShape(itemData.styledShapes[i], itemData.sh)
      }
    }
  }

  CVShapeElement.prototype.renderFill = function (styleData, itemData, groupTransform) {
    var styleElem = itemData.style

    if (itemData.c._mdf || this._isFirstFrame) {
      styleElem.co =
        'rgb(' + bmFloor(itemData.c.v[0]) + ',' + bmFloor(itemData.c.v[1]) + ',' + bmFloor(itemData.c.v[2]) + ')'
    }
    if (itemData.o._mdf || groupTransform._opMdf || this._isFirstFrame) {
      styleElem.coOp = itemData.o.v * groupTransform.opacity
    }
  }

  CVShapeElement.prototype.renderGradientFill = function (styleData, itemData, groupTransform) {
    var styleElem = itemData.style
    var grd
    if (
      !styleElem.grd ||
      itemData.g._mdf ||
      itemData.s._mdf ||
      itemData.e._mdf ||
      (styleData.t !== 1 && (itemData.h._mdf || itemData.a._mdf))
    ) {
      var ctx = this.globalData.canvasContext
      var pt1 = itemData.s.v
      var pt2 = itemData.e.v
      if (styleData.t === 1) {
        grd = ctx.createLinearGradient(pt1[0], pt1[1], pt2[0], pt2[1])
      } else {
        var rad = Math.sqrt(Math.pow(pt1[0] - pt2[0], 2) + Math.pow(pt1[1] - pt2[1], 2))
        var ang = Math.atan2(pt2[1] - pt1[1], pt2[0] - pt1[0])

        var percent = itemData.h.v
        if (percent >= 1) {
          percent = 0.99
        } else if (percent <= -1) {
          percent = -0.99
        }
        var dist = rad * percent
        var x = Math.cos(ang + itemData.a.v) * dist + pt1[0]
        var y = Math.sin(ang + itemData.a.v) * dist + pt1[1]
        grd = ctx.createRadialGradient(x, y, 0, pt1[0], pt1[1], rad)
      }

      var i
      var len = styleData.g.p
      var cValues = itemData.g.c
      var opacity = 1

      for (i = 0; i < len; i += 1) {
        if (itemData.g._hasOpacity && itemData.g._collapsable) {
          opacity = itemData.g.o[i * 2 + 1]
        }
        grd.addColorStop(
          cValues[i * 4] / 100,
          'rgba(' + cValues[i * 4 + 1] + ',' + cValues[i * 4 + 2] + ',' + cValues[i * 4 + 3] + ',' + opacity + ')',
        )
      }
      styleElem.grd = grd
    }
    styleElem.coOp = itemData.o.v * groupTransform.opacity
  }

  CVShapeElement.prototype.renderStroke = function (styleData, itemData, groupTransform) {
    var styleElem = itemData.style
    var d = itemData.d
    if (d && (d._mdf || this._isFirstFrame)) {
      styleElem.da = d.dashArray
      styleElem.do = d.dashoffset[0]
    }
    if (itemData.c._mdf || this._isFirstFrame) {
      styleElem.co =
        'rgb(' + bmFloor(itemData.c.v[0]) + ',' + bmFloor(itemData.c.v[1]) + ',' + bmFloor(itemData.c.v[2]) + ')'
    }
    if (itemData.o._mdf || groupTransform._opMdf || this._isFirstFrame) {
      styleElem.coOp = itemData.o.v * groupTransform.opacity
    }
    if (itemData.w._mdf || this._isFirstFrame) {
      styleElem.wi = itemData.w.v
    }
  }

  CVShapeElement.prototype.destroy = function () {
    this.shapesData = null
    this.globalData = null
    this.canvasContext = null
    this.stylesList.length = 0
    this.itemsData.length = 0
  }

  function CVTextElement(data, globalData, comp) {
    this.textSpans = []
    this.yOffset = 0
    this.fillColorAnim = false
    this.strokeColorAnim = false
    this.strokeWidthAnim = false
    this.stroke = false
    this.fill = false
    this.justifyOffset = 0
    this.currentRender = null
    this.renderType = 'canvas'
    this.values = {
      fill: 'rgba(0,0,0,0)',
      stroke: 'rgba(0,0,0,0)',
      sWidth: 0,
      fValue: '',
    }
    this.initElement(data, globalData, comp)
  }
  extendPrototype(
    [BaseElement, TransformElement, CVBaseElement, HierarchyElement, FrameElement, RenderableElement, ITextElement],
    CVTextElement,
  )

  CVTextElement.prototype.tHelper = createTag('canvas').getContext('2d')

  CVTextElement.prototype.buildNewText = function () {
    var documentData = this.textProperty.currentData
    this.renderedLetters = createSizedArray(documentData.l ? documentData.l.length : 0)

    var hasFill = false
    if (documentData.fc) {
      hasFill = true
      this.values.fill = this.buildColor(documentData.fc)
    } else {
      this.values.fill = 'rgba(0,0,0,0)'
    }
    this.fill = hasFill
    var hasStroke = false
    if (documentData.sc) {
      hasStroke = true
      this.values.stroke = this.buildColor(documentData.sc)
      this.values.sWidth = documentData.sw
    }
    var fontData = this.globalData.fontManager.getFontByName(documentData.f)
    var i
    var len
    var letters = documentData.l
    var matrixHelper = this.mHelper
    this.stroke = hasStroke
    this.values.fValue =
      documentData.finalSize + 'px ' + this.globalData.fontManager.getFontByName(documentData.f).fFamily
    len = documentData.finalText.length
    // this.tHelper.font = this.values.fValue;
    var charData
    var shapeData
    var k
    var kLen
    var shapes
    var j
    var jLen
    var pathNodes
    var commands
    var pathArr
    var singleShape = this.data.singleShape
    var trackingOffset = documentData.tr * 0.001 * documentData.finalSize
    var xPos = 0
    var yPos = 0
    var firstLine = true
    var cnt = 0
    for (i = 0; i < len; i += 1) {
      charData = this.globalData.fontManager.getCharData(
        documentData.finalText[i],
        fontData.fStyle,
        this.globalData.fontManager.getFontByName(documentData.f).fFamily,
      )
      shapeData = (charData && charData.data) || {}
      matrixHelper.reset()
      if (singleShape && letters[i].n) {
        xPos = -trackingOffset
        yPos += documentData.yOffset
        yPos += firstLine ? 1 : 0
        firstLine = false
      }
      shapes = shapeData.shapes ? shapeData.shapes[0].it : []
      jLen = shapes.length
      matrixHelper.scale(documentData.finalSize / 100, documentData.finalSize / 100)
      if (singleShape) {
        this.applyTextPropertiesToMatrix(documentData, matrixHelper, letters[i].line, xPos, yPos)
      }
      commands = createSizedArray(jLen - 1)
      var commandsCounter = 0
      for (j = 0; j < jLen; j += 1) {
        if (shapes[j].ty === 'sh') {
          kLen = shapes[j].ks.k.i.length
          pathNodes = shapes[j].ks.k
          pathArr = []
          for (k = 1; k < kLen; k += 1) {
            if (k === 1) {
              pathArr.push(
                matrixHelper.applyToX(pathNodes.v[0][0], pathNodes.v[0][1], 0),
                matrixHelper.applyToY(pathNodes.v[0][0], pathNodes.v[0][1], 0),
              )
            }
            pathArr.push(
              matrixHelper.applyToX(pathNodes.o[k - 1][0], pathNodes.o[k - 1][1], 0),
              matrixHelper.applyToY(pathNodes.o[k - 1][0], pathNodes.o[k - 1][1], 0),
              matrixHelper.applyToX(pathNodes.i[k][0], pathNodes.i[k][1], 0),
              matrixHelper.applyToY(pathNodes.i[k][0], pathNodes.i[k][1], 0),
              matrixHelper.applyToX(pathNodes.v[k][0], pathNodes.v[k][1], 0),
              matrixHelper.applyToY(pathNodes.v[k][0], pathNodes.v[k][1], 0),
            )
          }
          pathArr.push(
            matrixHelper.applyToX(pathNodes.o[k - 1][0], pathNodes.o[k - 1][1], 0),
            matrixHelper.applyToY(pathNodes.o[k - 1][0], pathNodes.o[k - 1][1], 0),
            matrixHelper.applyToX(pathNodes.i[0][0], pathNodes.i[0][1], 0),
            matrixHelper.applyToY(pathNodes.i[0][0], pathNodes.i[0][1], 0),
            matrixHelper.applyToX(pathNodes.v[0][0], pathNodes.v[0][1], 0),
            matrixHelper.applyToY(pathNodes.v[0][0], pathNodes.v[0][1], 0),
          )
          commands[commandsCounter] = pathArr
          commandsCounter += 1
        }
      }
      if (singleShape) {
        xPos += letters[i].l
        xPos += trackingOffset
      }
      if (this.textSpans[cnt]) {
        this.textSpans[cnt].elem = commands
      } else {
        this.textSpans[cnt] = { elem: commands }
      }
      cnt += 1
    }
  }

  CVTextElement.prototype.renderInnerContent = function () {
    var ctx = this.canvasContext
    ctx.font = this.values.fValue
    ctx.lineCap = 'butt'
    ctx.lineJoin = 'miter'
    ctx.miterLimit = 4

    if (!this.data.singleShape) {
      this.textAnimator.getMeasures(this.textProperty.currentData, this.lettersChangedFlag)
    }

    var i
    var len
    var j
    var jLen
    var k
    var kLen
    var renderedLetters = this.textAnimator.renderedLetters

    var letters = this.textProperty.currentData.l

    len = letters.length
    var renderedLetter
    var lastFill = null
    var lastStroke = null
    var lastStrokeW = null
    var commands
    var pathArr
    for (i = 0; i < len; i += 1) {
      if (!letters[i].n) {
        renderedLetter = renderedLetters[i]
        if (renderedLetter) {
          this.globalData.renderer.save()
          this.globalData.renderer.ctxTransform(renderedLetter.p)
          this.globalData.renderer.ctxOpacity(renderedLetter.o)
        }
        if (this.fill) {
          if (renderedLetter && renderedLetter.fc) {
            if (lastFill !== renderedLetter.fc) {
              lastFill = renderedLetter.fc
              ctx.fillStyle = renderedLetter.fc
            }
          } else if (lastFill !== this.values.fill) {
            lastFill = this.values.fill
            ctx.fillStyle = this.values.fill
          }
          commands = this.textSpans[i].elem
          jLen = commands.length
          this.globalData.canvasContext.beginPath()
          for (j = 0; j < jLen; j += 1) {
            pathArr = commands[j]
            kLen = pathArr.length
            this.globalData.canvasContext.moveTo(pathArr[0], pathArr[1])
            for (k = 2; k < kLen; k += 6) {
              this.globalData.canvasContext.bezierCurveTo(
                pathArr[k],
                pathArr[k + 1],
                pathArr[k + 2],
                pathArr[k + 3],
                pathArr[k + 4],
                pathArr[k + 5],
              )
            }
          }
          this.globalData.canvasContext.closePath()
          this.globalData.canvasContext.fill()
          /// ctx.fillText(this.textSpans[i].val,0,0);
        }
        if (this.stroke) {
          if (renderedLetter && renderedLetter.sw) {
            if (lastStrokeW !== renderedLetter.sw) {
              lastStrokeW = renderedLetter.sw
              ctx.lineWidth = renderedLetter.sw
            }
          } else if (lastStrokeW !== this.values.sWidth) {
            lastStrokeW = this.values.sWidth
            ctx.lineWidth = this.values.sWidth
          }
          if (renderedLetter && renderedLetter.sc) {
            if (lastStroke !== renderedLetter.sc) {
              lastStroke = renderedLetter.sc
              ctx.strokeStyle = renderedLetter.sc
            }
          } else if (lastStroke !== this.values.stroke) {
            lastStroke = this.values.stroke
            ctx.strokeStyle = this.values.stroke
          }
          commands = this.textSpans[i].elem
          jLen = commands.length
          this.globalData.canvasContext.beginPath()
          for (j = 0; j < jLen; j += 1) {
            pathArr = commands[j]
            kLen = pathArr.length
            this.globalData.canvasContext.moveTo(pathArr[0], pathArr[1])
            for (k = 2; k < kLen; k += 6) {
              this.globalData.canvasContext.bezierCurveTo(
                pathArr[k],
                pathArr[k + 1],
                pathArr[k + 2],
                pathArr[k + 3],
                pathArr[k + 4],
                pathArr[k + 5],
              )
            }
          }
          this.globalData.canvasContext.closePath()
          this.globalData.canvasContext.stroke()
          /// ctx.strokeText(letters[i].val,0,0);
        }
        if (renderedLetter) {
          this.globalData.renderer.restore()
        }
      }
    }
  }

  function CVImageElement(data, globalData, comp) {
    this.assetData = globalData.getAssetData(data.refId)
    this.img = globalData.imageLoader.getAsset(this.assetData)
    this.initElement(data, globalData, comp)
  }
  extendPrototype(
    [BaseElement, TransformElement, CVBaseElement, HierarchyElement, FrameElement, RenderableElement],
    CVImageElement,
  )

  CVImageElement.prototype.initElement = SVGShapeElement.prototype.initElement
  CVImageElement.prototype.prepareFrame = IImageElement.prototype.prepareFrame

  CVImageElement.prototype.createContent = function () {
    if (this.img.width && (this.assetData.w !== this.img.width || this.assetData.h !== this.img.height)) {
      var canvas = createTag('canvas')
      canvas.width = this.assetData.w
      canvas.height = this.assetData.h
      var ctx = canvas.getContext('2d')

      var imgW = this.img.width
      var imgH = this.img.height
      var imgRel = imgW / imgH
      var canvasRel = this.assetData.w / this.assetData.h
      var widthCrop
      var heightCrop
      var par = this.assetData.pr || this.globalData.renderConfig.imagePreserveAspectRatio
      if ((imgRel > canvasRel && par === 'xMidYMid slice') || (imgRel < canvasRel && par !== 'xMidYMid slice')) {
        heightCrop = imgH
        widthCrop = heightCrop * canvasRel
      } else {
        widthCrop = imgW
        heightCrop = widthCrop / canvasRel
      }
      ctx.drawImage(
        this.img,
        (imgW - widthCrop) / 2,
        (imgH - heightCrop) / 2,
        widthCrop,
        heightCrop,
        0,
        0,
        this.assetData.w,
        this.assetData.h,
      )
      this.img = canvas
    }
  }

  CVImageElement.prototype.renderInnerContent = function () {
    this.canvasContext.drawImage(this.img, 0, 0)
  }

  CVImageElement.prototype.destroy = function () {
    this.img = null
  }

  function CVSolidElement(data, globalData, comp) {
    this.initElement(data, globalData, comp)
  }
  extendPrototype(
    [BaseElement, TransformElement, CVBaseElement, HierarchyElement, FrameElement, RenderableElement],
    CVSolidElement,
  )

  CVSolidElement.prototype.initElement = SVGShapeElement.prototype.initElement
  CVSolidElement.prototype.prepareFrame = IImageElement.prototype.prepareFrame

  CVSolidElement.prototype.renderInnerContent = function () {
    var ctx = this.canvasContext
    ctx.fillStyle = this.data.sc
    ctx.fillRect(0, 0, this.data.sw, this.data.sh)
    //
  }

  function CanvasRendererBase(animationItem, config) {
    this.animationItem = animationItem
    this.renderConfig = {
      clearCanvas: config && config.clearCanvas !== undefined ? config.clearCanvas : true,
      context: (config && config.context) || null,
      progressiveLoad: (config && config.progressiveLoad) || false,
      preserveAspectRatio: (config && config.preserveAspectRatio) || 'xMidYMid meet',
      imagePreserveAspectRatio: (config && config.imagePreserveAspectRatio) || 'xMidYMid slice',
      contentVisibility: (config && config.contentVisibility) || 'visible',
      className: (config && config.className) || '',
      id: (config && config.id) || '',
    }
    this.renderConfig.dpr = (config && config.dpr) || 1
    if (this.animationItem.wrapper) {
      this.renderConfig.dpr = (config && config.dpr) || window.devicePixelRatio || 1
    }
    this.renderedFrame = -1
    this.globalData = {
      frameNum: -1,
      _mdf: false,
      renderConfig: this.renderConfig,
      currentGlobalAlpha: -1,
    }
    this.contextData = new CVContextData()
    this.elements = []
    this.pendingElements = []
    this.transformMat = new Matrix()
    this.completeLayers = false
    this.rendererType = 'canvas'
  }
  extendPrototype([BaseRenderer], CanvasRendererBase)

  CanvasRendererBase.prototype.createShape = function (data) {
    return new CVShapeElement(data, this.globalData, this)
  }

  CanvasRendererBase.prototype.createText = function (data) {
    return new CVTextElement(data, this.globalData, this)
  }

  CanvasRendererBase.prototype.createImage = function (data) {
    return new CVImageElement(data, this.globalData, this)
  }

  CanvasRendererBase.prototype.createSolid = function (data) {
    return new CVSolidElement(data, this.globalData, this)
  }

  CanvasRendererBase.prototype.createNull = SVGRenderer.prototype.createNull

  CanvasRendererBase.prototype.ctxTransform = function (props) {
    if (props[0] === 1 && props[1] === 0 && props[4] === 0 && props[5] === 1 && props[12] === 0 && props[13] === 0) {
      return
    }
    if (!this.renderConfig.clearCanvas) {
      this.canvasContext.transform(props[0], props[1], props[4], props[5], props[12], props[13])
      return
    }
    this.transformMat.cloneFromProps(props)
    var cProps = this.contextData.cTr.props
    this.transformMat.transform(
      cProps[0],
      cProps[1],
      cProps[2],
      cProps[3],
      cProps[4],
      cProps[5],
      cProps[6],
      cProps[7],
      cProps[8],
      cProps[9],
      cProps[10],
      cProps[11],
      cProps[12],
      cProps[13],
      cProps[14],
      cProps[15],
    )
    // this.contextData.cTr.transform(props[0],props[1],props[2],props[3],props[4],props[5],props[6],props[7],props[8],props[9],props[10],props[11],props[12],props[13],props[14],props[15]);
    this.contextData.cTr.cloneFromProps(this.transformMat.props)
    var trProps = this.contextData.cTr.props
    this.canvasContext.setTransform(trProps[0], trProps[1], trProps[4], trProps[5], trProps[12], trProps[13])
  }

  CanvasRendererBase.prototype.ctxOpacity = function (op) {
    /* if(op === 1){
        return;
    } */
    if (!this.renderConfig.clearCanvas) {
      this.canvasContext.globalAlpha *= op < 0 ? 0 : op
      this.globalData.currentGlobalAlpha = this.contextData.cO
      return
    }
    this.contextData.cO *= op < 0 ? 0 : op
    if (this.globalData.currentGlobalAlpha !== this.contextData.cO) {
      this.canvasContext.globalAlpha = this.contextData.cO
      this.globalData.currentGlobalAlpha = this.contextData.cO
    }
  }

  CanvasRendererBase.prototype.reset = function () {
    if (!this.renderConfig.clearCanvas) {
      this.canvasContext.restore()
      return
    }
    this.contextData.reset()
  }

  CanvasRendererBase.prototype.save = function (actionFlag) {
    if (!this.renderConfig.clearCanvas) {
      this.canvasContext.save()
      return
    }
    if (actionFlag) {
      this.canvasContext.save()
    }
    var props = this.contextData.cTr.props
    if (this.contextData._length <= this.contextData.cArrPos) {
      this.contextData.duplicate()
    }
    var i
    var arr = this.contextData.saved[this.contextData.cArrPos]
    for (i = 0; i < 16; i += 1) {
      arr[i] = props[i]
    }
    this.contextData.savedOp[this.contextData.cArrPos] = this.contextData.cO
    this.contextData.cArrPos += 1
  }

  CanvasRendererBase.prototype.restore = function (actionFlag) {
    if (!this.renderConfig.clearCanvas) {
      this.canvasContext.restore()
      return
    }
    if (actionFlag) {
      this.canvasContext.restore()
      this.globalData.blendMode = 'source-over'
    }
    this.contextData.cArrPos -= 1
    var popped = this.contextData.saved[this.contextData.cArrPos]
    var i
    var arr = this.contextData.cTr.props
    for (i = 0; i < 16; i += 1) {
      arr[i] = popped[i]
    }
    this.canvasContext.setTransform(popped[0], popped[1], popped[4], popped[5], popped[12], popped[13])
    popped = this.contextData.savedOp[this.contextData.cArrPos]
    this.contextData.cO = popped
    if (this.globalData.currentGlobalAlpha !== popped) {
      this.canvasContext.globalAlpha = popped
      this.globalData.currentGlobalAlpha = popped
    }
  }

  CanvasRendererBase.prototype.configAnimation = function (animData) {
    if (this.animationItem.wrapper) {
      this.animationItem.container = createTag('canvas')
      var containerStyle = this.animationItem.container.style
      containerStyle.width = '100%'
      containerStyle.height = '100%'
      var origin = '0px 0px 0px'
      containerStyle.transformOrigin = origin
      containerStyle.mozTransformOrigin = origin
      containerStyle.webkitTransformOrigin = origin
      containerStyle['-webkit-transform'] = origin
      containerStyle.contentVisibility = this.renderConfig.contentVisibility
      this.animationItem.wrapper.appendChild(this.animationItem.container)
      this.canvasContext = this.animationItem.container.getContext('2d')
      if (this.renderConfig.className) {
        this.animationItem.container.setAttribute('class', this.renderConfig.className)
      }
      if (this.renderConfig.id) {
        this.animationItem.container.setAttribute('id', this.renderConfig.id)
      }
    } else {
      this.canvasContext = this.renderConfig.context
    }
    this.data = animData
    this.layers = animData.layers
    this.transformCanvas = {
      w: animData.w,
      h: animData.h,
      sx: 0,
      sy: 0,
      tx: 0,
      ty: 0,
    }
    this.setupGlobalData(animData, document.body)
    this.globalData.canvasContext = this.canvasContext
    this.globalData.renderer = this
    this.globalData.isDashed = false
    this.globalData.progressiveLoad = this.renderConfig.progressiveLoad
    this.globalData.transformCanvas = this.transformCanvas
    this.elements = createSizedArray(animData.layers.length)

    this.updateContainerSize()
  }

  CanvasRendererBase.prototype.updateContainerSize = function () {
    this.reset()
    var elementWidth
    var elementHeight
    if (this.animationItem.wrapper && this.animationItem.container) {
      elementWidth = this.animationItem.wrapper.offsetWidth
      elementHeight = this.animationItem.wrapper.offsetHeight
      this.animationItem.container.setAttribute('width', elementWidth * this.renderConfig.dpr)
      this.animationItem.container.setAttribute('height', elementHeight * this.renderConfig.dpr)
    } else {
      elementWidth = this.canvasContext.canvas.width * this.renderConfig.dpr
      elementHeight = this.canvasContext.canvas.height * this.renderConfig.dpr
    }
    var elementRel
    var animationRel
    if (
      this.renderConfig.preserveAspectRatio.indexOf('meet') !== -1 ||
      this.renderConfig.preserveAspectRatio.indexOf('slice') !== -1
    ) {
      var par = this.renderConfig.preserveAspectRatio.split(' ')
      var fillType = par[1] || 'meet'
      var pos = par[0] || 'xMidYMid'
      var xPos = pos.substr(0, 4)
      var yPos = pos.substr(4)
      elementRel = elementWidth / elementHeight
      animationRel = this.transformCanvas.w / this.transformCanvas.h
      if ((animationRel > elementRel && fillType === 'meet') || (animationRel < elementRel && fillType === 'slice')) {
        this.transformCanvas.sx = elementWidth / (this.transformCanvas.w / this.renderConfig.dpr)
        this.transformCanvas.sy = elementWidth / (this.transformCanvas.w / this.renderConfig.dpr)
      } else {
        this.transformCanvas.sx = elementHeight / (this.transformCanvas.h / this.renderConfig.dpr)
        this.transformCanvas.sy = elementHeight / (this.transformCanvas.h / this.renderConfig.dpr)
      }

      if (
        xPos === 'xMid' &&
        ((animationRel < elementRel && fillType === 'meet') || (animationRel > elementRel && fillType === 'slice'))
      ) {
        this.transformCanvas.tx =
          ((elementWidth - this.transformCanvas.w * (elementHeight / this.transformCanvas.h)) / 2) *
          this.renderConfig.dpr
      } else if (
        xPos === 'xMax' &&
        ((animationRel < elementRel && fillType === 'meet') || (animationRel > elementRel && fillType === 'slice'))
      ) {
        this.transformCanvas.tx =
          (elementWidth - this.transformCanvas.w * (elementHeight / this.transformCanvas.h)) * this.renderConfig.dpr
      } else {
        this.transformCanvas.tx = 0
      }
      if (
        yPos === 'YMid' &&
        ((animationRel > elementRel && fillType === 'meet') || (animationRel < elementRel && fillType === 'slice'))
      ) {
        this.transformCanvas.ty =
          ((elementHeight - this.transformCanvas.h * (elementWidth / this.transformCanvas.w)) / 2) *
          this.renderConfig.dpr
      } else if (
        yPos === 'YMax' &&
        ((animationRel > elementRel && fillType === 'meet') || (animationRel < elementRel && fillType === 'slice'))
      ) {
        this.transformCanvas.ty =
          (elementHeight - this.transformCanvas.h * (elementWidth / this.transformCanvas.w)) * this.renderConfig.dpr
      } else {
        this.transformCanvas.ty = 0
      }
    } else if (this.renderConfig.preserveAspectRatio === 'none') {
      this.transformCanvas.sx = elementWidth / (this.transformCanvas.w / this.renderConfig.dpr)
      this.transformCanvas.sy = elementHeight / (this.transformCanvas.h / this.renderConfig.dpr)
      this.transformCanvas.tx = 0
      this.transformCanvas.ty = 0
    } else {
      this.transformCanvas.sx = this.renderConfig.dpr
      this.transformCanvas.sy = this.renderConfig.dpr
      this.transformCanvas.tx = 0
      this.transformCanvas.ty = 0
    }
    this.transformCanvas.props = [
      this.transformCanvas.sx,
      0,
      0,
      0,
      0,
      this.transformCanvas.sy,
      0,
      0,
      0,
      0,
      1,
      0,
      this.transformCanvas.tx,
      this.transformCanvas.ty,
      0,
      1,
    ]
    /* var i, len = this.elements.length;
    for(i=0;i<len;i+=1){
        if(this.elements[i] && this.elements[i].data.ty === 0){
            this.elements[i].resize(this.globalData.transformCanvas);
        }
    } */
    this.ctxTransform(this.transformCanvas.props)
    this.canvasContext.beginPath()
    this.canvasContext.rect(0, 0, this.transformCanvas.w, this.transformCanvas.h)
    this.canvasContext.closePath()
    this.canvasContext.clip()

    this.renderFrame(this.renderedFrame, true)
  }

  CanvasRendererBase.prototype.destroy = function () {
    if (this.renderConfig.clearCanvas && this.animationItem.wrapper) {
      this.animationItem.wrapper.innerText = ''
    }
    var i
    var len = this.layers ? this.layers.length : 0
    for (i = len - 1; i >= 0; i -= 1) {
      if (this.elements[i]) {
        this.elements[i].destroy()
      }
    }
    this.elements.length = 0
    this.globalData.canvasContext = null
    this.animationItem.container = null
    this.destroyed = true
  }

  CanvasRendererBase.prototype.renderFrame = function (num, forceRender) {
    if (
      (this.renderedFrame === num && this.renderConfig.clearCanvas === true && !forceRender) ||
      this.destroyed ||
      num === -1
    ) {
      return
    }
    this.renderedFrame = num
    this.globalData.frameNum = num - this.animationItem._isFirstFrame
    this.globalData.frameId += 1
    this.globalData._mdf = !this.renderConfig.clearCanvas || forceRender
    this.globalData.projectInterface.currentFrame = num

    // console.log('--------');
    // console.log('NEW: ',num);
    var i
    var len = this.layers.length
    if (!this.completeLayers) {
      this.checkLayers(num)
    }

    for (i = 0; i < len; i += 1) {
      if (this.completeLayers || this.elements[i]) {
        this.elements[i].prepareFrame(num - this.layers[i].st)
      }
    }
    if (this.globalData._mdf) {
      if (this.renderConfig.clearCanvas === true) {
        this.canvasContext.clearRect(0, 0, this.transformCanvas.w, this.transformCanvas.h)
      } else {
        this.save()
      }
      for (i = len - 1; i >= 0; i -= 1) {
        if (this.completeLayers || this.elements[i]) {
          this.elements[i].renderFrame()
        }
      }
      if (this.renderConfig.clearCanvas !== true) {
        this.restore()
      }
    }
  }

  CanvasRendererBase.prototype.buildItem = function (pos) {
    var elements = this.elements
    if (elements[pos] || this.layers[pos].ty === 99) {
      return
    }
    var element = this.createItem(this.layers[pos], this, this.globalData)
    elements[pos] = element
    element.initExpressions()
    /* if(this.layers[pos].ty === 0){
        element.resize(this.globalData.transformCanvas);
    } */
  }

  CanvasRendererBase.prototype.checkPendingElements = function () {
    while (this.pendingElements.length) {
      var element = this.pendingElements.pop()
      element.checkParenting()
    }
  }

  CanvasRendererBase.prototype.hide = function () {
    this.animationItem.container.style.display = 'none'
  }

  CanvasRendererBase.prototype.show = function () {
    this.animationItem.container.style.display = 'block'
  }

  function CVCompElement(data, globalData, comp) {
    this.completeLayers = false
    this.layers = data.layers
    this.pendingElements = []
    this.elements = createSizedArray(this.layers.length)
    this.initElement(data, globalData, comp)
    this.tm = data.tm ? PropertyFactory.getProp(this, data.tm, 0, globalData.frameRate, this) : { _placeholder: true }
  }

  extendPrototype([CanvasRendererBase, ICompElement, CVBaseElement], CVCompElement)

  CVCompElement.prototype.renderInnerContent = function () {
    var ctx = this.canvasContext
    ctx.beginPath()
    ctx.moveTo(0, 0)
    ctx.lineTo(this.data.w, 0)
    ctx.lineTo(this.data.w, this.data.h)
    ctx.lineTo(0, this.data.h)
    ctx.lineTo(0, 0)
    ctx.clip()
    var i
    var len = this.layers.length
    for (i = len - 1; i >= 0; i -= 1) {
      if (this.completeLayers || this.elements[i]) {
        this.elements[i].renderFrame()
      }
    }
  }

  CVCompElement.prototype.destroy = function () {
    var i
    var len = this.layers.length
    for (i = len - 1; i >= 0; i -= 1) {
      if (this.elements[i]) {
        this.elements[i].destroy()
      }
    }
    this.layers = null
    this.elements = null
  }

  CVCompElement.prototype.createComp = function (data) {
    return new CVCompElement(data, this.globalData, this)
  }

  function CanvasRenderer(animationItem, config) {
    this.animationItem = animationItem
    this.renderConfig = {
      clearCanvas: config && config.clearCanvas !== undefined ? config.clearCanvas : true,
      context: (config && config.context) || null,
      progressiveLoad: (config && config.progressiveLoad) || false,
      preserveAspectRatio: (config && config.preserveAspectRatio) || 'xMidYMid meet',
      imagePreserveAspectRatio: (config && config.imagePreserveAspectRatio) || 'xMidYMid slice',
      contentVisibility: (config && config.contentVisibility) || 'visible',
      className: (config && config.className) || '',
      id: (config && config.id) || '',
    }
    this.renderConfig.dpr = (config && config.dpr) || 1
    if (this.animationItem.wrapper) {
      this.renderConfig.dpr = (config && config.dpr) || window.devicePixelRatio || 1
    }
    this.renderedFrame = -1
    this.globalData = {
      frameNum: -1,
      _mdf: false,
      renderConfig: this.renderConfig,
      currentGlobalAlpha: -1,
    }
    this.contextData = new CVContextData()
    this.elements = []
    this.pendingElements = []
    this.transformMat = new Matrix()
    this.completeLayers = false
    this.rendererType = 'canvas'
  }
  extendPrototype([CanvasRendererBase], CanvasRenderer)

  CanvasRenderer.prototype.createComp = function (data) {
    return new CVCompElement(data, this.globalData, this)
  }

  // Registering renderers
  registerRenderer('canvas', CanvasRenderer)

  // Registering shape modifiers
  ShapeModifiers.registerModifier('tm', TrimModifier)
  ShapeModifiers.registerModifier('pb', PuckerAndBloatModifier)
  ShapeModifiers.registerModifier('rp', RepeaterModifier)
  ShapeModifiers.registerModifier('rd', RoundCornersModifier)

  const Expressions = (function () {
    var ob = {}
    ob.initExpressions = initExpressions

    function initExpressions(animation) {
      var stackCount = 0
      var registers = []

      function pushExpression() {
        stackCount += 1
      }

      function popExpression() {
        stackCount -= 1
        if (stackCount === 0) {
          releaseInstances()
        }
      }

      function registerExpressionProperty(expression) {
        if (registers.indexOf(expression) === -1) {
          registers.push(expression)
        }
      }

      function releaseInstances() {
        var i
        var len = registers.length
        for (i = 0; i < len; i += 1) {
          registers[i].release()
        }
        registers.length = 0
      }

      animation.renderer.compInterface = CompExpressionInterface(animation.renderer)
      animation.renderer.globalData.projectInterface.registerComposition(animation.renderer)
      animation.renderer.globalData.pushExpression = pushExpression
      animation.renderer.globalData.popExpression = popExpression
      animation.renderer.globalData.registerExpressionProperty = registerExpressionProperty
    }
    return ob
  })()

  /* eslint-disable */
  /*
 Copyright 2014 David Bau.

 Permission is hereby granted, free of charge, to any person obtaining
 a copy of this software and associated documentation files (the
 "Software"), to deal in the Software without restriction, including
 without limitation the rights to use, copy, modify, merge, publish,
 distribute, sublicense, and/or sell copies of the Software, and to
 permit persons to whom the Software is furnished to do so, subject to
 the following conditions:

 The above copyright notice and this permission notice shall be
 included in all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

 */

  function seedRandom(pool, math) {
    //
    // The following constants are related to IEEE 754 limits.
    //
    var global = this,
      width = 256, // each RC4 output is 0 <= x < 256
      chunks = 6, // at least six RC4 outputs for each double
      digits = 52, // there are 52 significant digits in a double
      rngname = 'random', // rngname: name for Math.random and Math.seedrandom
      startdenom = math.pow(width, chunks),
      significance = math.pow(2, digits),
      overflow = significance * 2,
      mask = width - 1,
      nodecrypto // node.js crypto module, initialized at the bottom.

    //
    // seedrandom()
    // This is the seedrandom function described above.
    //
    function seedrandom(seed, options, callback) {
      var key = []
      options = options === true ? { entropy: true } : options || {}

      // Flatten the seed string or build one from local entropy if needed.
      var shortseed = mixkey(
        flatten(options.entropy ? [seed, tostring(pool)] : seed === null ? autoseed() : seed, 3),
        key,
      )

      // Use the seed to initialize an ARC4 generator.
      var arc4 = new ARC4(key)

      // This function returns a random double in [0, 1) that contains
      // randomness in every bit of the mantissa of the IEEE 754 value.
      var prng = function () {
        var n = arc4.g(chunks), // Start with a numerator n < 2 ^ 48
          d = startdenom, //   and denominator d = 2 ^ 48.
          x = 0 //   and no 'extra last byte'.
        while (n < significance) {
          // Fill up all significant digits by
          n = (n + x) * width //   shifting numerator and
          d *= width //   denominator and generating a
          x = arc4.g(1) //   new least-significant-byte.
        }
        while (n >= overflow) {
          // To avoid rounding up, before adding
          n /= 2 //   last byte, shift everything
          d /= 2 //   right using integer math until
          x >>>= 1 //   we have exactly the desired bits.
        }
        return (n + x) / d // Form the number within [0, 1).
      }

      prng.int32 = function () {
        return arc4.g(4) | 0
      }
      prng.quick = function () {
        return arc4.g(4) / 0x100000000
      }
      prng.double = prng

      // Mix the randomness into accumulated entropy.
      mixkey(tostring(arc4.S), pool)

      // Calling convention: what to return as a function of prng, seed, is_math.
      return (
        options.pass ||
        callback ||
        function (prng, seed, is_math_call, state) {
          if (state) {
            // Load the arc4 state from the given state if it has an S array.
            if (state.S) {
              copy(state, arc4)
            }
            // Only provide the .state method if requested via options.state.
            prng.state = function () {
              return copy(arc4, {})
            }
          }

          // If called as a method of Math (Math.seedrandom()), mutate
          // Math.random because that is how seedrandom.js has worked since v1.0.
          if (is_math_call) {
            math[rngname] = prng
            return seed
          }

          // Otherwise, it is a newer calling convention, so return the
          // prng directly.
          else return prng
        }
      )(prng, shortseed, 'global' in options ? options.global : this == math, options.state)
    }
    math['seed' + rngname] = seedrandom

    //
    // ARC4
    //
    // An ARC4 implementation.  The constructor takes a key in the form of
    // an array of at most (width) integers that should be 0 <= x < (width).
    //
    // The g(count) method returns a pseudorandom integer that concatenates
    // the next (count) outputs from ARC4.  Its return value is a number x
    // that is in the range 0 <= x < (width ^ count).
    //
    function ARC4(key) {
      var t,
        keylen = key.length,
        me = this,
        i = 0,
        j = (me.i = me.j = 0),
        s = (me.S = [])

      // The empty key [] is treated as [0].
      if (!keylen) {
        key = [keylen++]
      }

      // Set up S using the standard key scheduling algorithm.
      while (i < width) {
        s[i] = i++
      }
      for (i = 0; i < width; i++) {
        s[i] = s[(j = mask & (j + key[i % keylen] + (t = s[i])))]
        s[j] = t
      }

      // The "g" method returns the next (count) outputs as one number.
      me.g = function (count) {
        // Using instance members instead of closure state nearly doubles speed.
        var t,
          r = 0,
          i = me.i,
          j = me.j,
          s = me.S
        while (count--) {
          t = s[(i = mask & (i + 1))]
          r = r * width + s[mask & ((s[i] = s[(j = mask & (j + t))]) + (s[j] = t))]
        }
        me.i = i
        me.j = j
        return r
        // For robust unpredictability, the function call below automatically
        // discards an initial batch of values.  This is called RC4-drop[256].
        // See http://google.com/search?q=rsa+fluhrer+response&btnI
      }
    }

    //
    // copy()
    // Copies internal state of ARC4 to or from a plain object.
    //
    function copy(f, t) {
      t.i = f.i
      t.j = f.j
      t.S = f.S.slice()
      return t
    }

    //
    // flatten()
    // Converts an object tree to nested arrays of strings.
    //
    function flatten(obj, depth) {
      var result = [],
        typ = typeof obj,
        prop
      if (depth && typ == 'object') {
        for (prop in obj) {
          try {
            result.push(flatten(obj[prop], depth - 1))
          } catch (e) {}
        }
      }
      return result.length ? result : typ == 'string' ? obj : obj + '\0'
    }

    //
    // mixkey()
    // Mixes a string seed into a key that is an array of integers, and
    // returns a shortened string seed that is equivalent to the result key.
    //
    function mixkey(seed, key) {
      var stringseed = seed + '',
        smear,
        j = 0
      while (j < stringseed.length) {
        key[mask & j] = mask & ((smear ^= key[mask & j] * 19) + stringseed.charCodeAt(j++))
      }
      return tostring(key)
    }

    //
    // autoseed()
    // Returns an object for autoseeding, using window.crypto and Node crypto
    // module if available.
    //
    function autoseed() {
      try {
        if (nodecrypto) {
          return tostring(nodecrypto.randomBytes(width))
        }
        var out = new Uint8Array(width)
        ;(global.crypto || global.msCrypto).getRandomValues(out)
        return tostring(out)
      } catch (e) {
        var browser = global.navigator,
          plugins = browser && browser.plugins
        return [+new Date(), global, plugins, global.screen, tostring(pool)]
      }
    }

    //
    // tostring()
    // Converts an array of charcodes to a string
    //
    function tostring(a) {
      return String.fromCharCode.apply(0, a)
    }

    //
    // When seedrandom.js is loaded, we immediately mix a few bits
    // from the built-in RNG into the entropy pool.  Because we do
    // not want to interfere with deterministic PRNG state later,
    // seedrandom will not call math.random on its own again after
    // initialization.
    //
    mixkey(math.random(), pool)

    //
    // Nodejs and AMD support: export the implementation as a module using
    // either convention.
    //

    // End anonymous scope, and pass initial values.
  }

  function initialize$2(BMMath) {
    seedRandom([], BMMath)
  }

  var propTypes = {
    SHAPE: 'shape',
  }

  /* eslint-disable camelcase */

  const ExpressionManager = (function () {
    'use strict'

    var ob = {}
    var Math = BMMath
    var window = null
    var document = null
    var XMLHttpRequest = null
    var fetch = null
    var frames = null
    initialize$2(BMMath)

    function $bm_isInstanceOfArray(arr) {
      return arr.constructor === Array || arr.constructor === Float32Array
    }

    function isNumerable(tOfV, v) {
      return tOfV === 'number' || tOfV === 'boolean' || tOfV === 'string' || v instanceof Number
    }

    function $bm_neg(a) {
      var tOfA = typeof a
      if (tOfA === 'number' || tOfA === 'boolean' || a instanceof Number) {
        return -a
      }
      if ($bm_isInstanceOfArray(a)) {
        var i
        var lenA = a.length
        var retArr = []
        for (i = 0; i < lenA; i += 1) {
          retArr[i] = -a[i]
        }
        return retArr
      }
      if (a.propType) {
        return a.v
      }
      return -a
    }

    var easeInBez = BezierFactory.getBezierEasing(0.333, 0, 0.833, 0.833, 'easeIn').get
    var easeOutBez = BezierFactory.getBezierEasing(0.167, 0.167, 0.667, 1, 'easeOut').get
    var easeInOutBez = BezierFactory.getBezierEasing(0.33, 0, 0.667, 1, 'easeInOut').get

    function sum(a, b) {
      var tOfA = typeof a
      var tOfB = typeof b
      if (tOfA === 'string' || tOfB === 'string') {
        return a + b
      }
      if (isNumerable(tOfA, a) && isNumerable(tOfB, b)) {
        return a + b
      }
      if ($bm_isInstanceOfArray(a) && isNumerable(tOfB, b)) {
        a = a.slice(0)
        a[0] += b
        return a
      }
      if (isNumerable(tOfA, a) && $bm_isInstanceOfArray(b)) {
        b = b.slice(0)
        b[0] = a + b[0]
        return b
      }
      if ($bm_isInstanceOfArray(a) && $bm_isInstanceOfArray(b)) {
        var i = 0
        var lenA = a.length
        var lenB = b.length
        var retArr = []
        while (i < lenA || i < lenB) {
          if (
            (typeof a[i] === 'number' || a[i] instanceof Number) &&
            (typeof b[i] === 'number' || b[i] instanceof Number)
          ) {
            retArr[i] = a[i] + b[i]
          } else {
            retArr[i] = b[i] === undefined ? a[i] : a[i] || b[i]
          }
          i += 1
        }
        return retArr
      }
      return 0
    }
    var add = sum

    function sub(a, b) {
      var tOfA = typeof a
      var tOfB = typeof b
      if (isNumerable(tOfA, a) && isNumerable(tOfB, b)) {
        if (tOfA === 'string') {
          a = parseInt(a, 10)
        }
        if (tOfB === 'string') {
          b = parseInt(b, 10)
        }
        return a - b
      }
      if ($bm_isInstanceOfArray(a) && isNumerable(tOfB, b)) {
        a = a.slice(0)
        a[0] -= b
        return a
      }
      if (isNumerable(tOfA, a) && $bm_isInstanceOfArray(b)) {
        b = b.slice(0)
        b[0] = a - b[0]
        return b
      }
      if ($bm_isInstanceOfArray(a) && $bm_isInstanceOfArray(b)) {
        var i = 0
        var lenA = a.length
        var lenB = b.length
        var retArr = []
        while (i < lenA || i < lenB) {
          if (
            (typeof a[i] === 'number' || a[i] instanceof Number) &&
            (typeof b[i] === 'number' || b[i] instanceof Number)
          ) {
            retArr[i] = a[i] - b[i]
          } else {
            retArr[i] = b[i] === undefined ? a[i] : a[i] || b[i]
          }
          i += 1
        }
        return retArr
      }
      return 0
    }

    function mul(a, b) {
      var tOfA = typeof a
      var tOfB = typeof b
      var arr
      if (isNumerable(tOfA, a) && isNumerable(tOfB, b)) {
        return a * b
      }

      var i
      var len
      if ($bm_isInstanceOfArray(a) && isNumerable(tOfB, b)) {
        len = a.length
        arr = createTypedArray('float32', len)
        for (i = 0; i < len; i += 1) {
          arr[i] = a[i] * b
        }
        return arr
      }
      if (isNumerable(tOfA, a) && $bm_isInstanceOfArray(b)) {
        len = b.length
        arr = createTypedArray('float32', len)
        for (i = 0; i < len; i += 1) {
          arr[i] = a * b[i]
        }
        return arr
      }
      return 0
    }

    function div(a, b) {
      var tOfA = typeof a
      var tOfB = typeof b
      var arr
      if (isNumerable(tOfA, a) && isNumerable(tOfB, b)) {
        return a / b
      }
      var i
      var len
      if ($bm_isInstanceOfArray(a) && isNumerable(tOfB, b)) {
        len = a.length
        arr = createTypedArray('float32', len)
        for (i = 0; i < len; i += 1) {
          arr[i] = a[i] / b
        }
        return arr
      }
      if (isNumerable(tOfA, a) && $bm_isInstanceOfArray(b)) {
        len = b.length
        arr = createTypedArray('float32', len)
        for (i = 0; i < len; i += 1) {
          arr[i] = a / b[i]
        }
        return arr
      }
      return 0
    }
    function mod(a, b) {
      if (typeof a === 'string') {
        a = parseInt(a, 10)
      }
      if (typeof b === 'string') {
        b = parseInt(b, 10)
      }
      return a % b
    }
    var $bm_sum = sum
    var $bm_sub = sub
    var $bm_mul = mul
    var $bm_div = div
    var $bm_mod = mod

    function clamp(num, min, max) {
      if (min > max) {
        var mm = max
        max = min
        min = mm
      }
      return Math.min(Math.max(num, min), max)
    }

    function radiansToDegrees(val) {
      return val / degToRads
    }
    var radians_to_degrees = radiansToDegrees

    function degreesToRadians(val) {
      return val * degToRads
    }
    var degrees_to_radians = radiansToDegrees

    var helperLengthArray = [0, 0, 0, 0, 0, 0]

    function length(arr1, arr2) {
      if (typeof arr1 === 'number' || arr1 instanceof Number) {
        arr2 = arr2 || 0
        return Math.abs(arr1 - arr2)
      }
      if (!arr2) {
        arr2 = helperLengthArray
      }
      var i
      var len = Math.min(arr1.length, arr2.length)
      var addedLength = 0
      for (i = 0; i < len; i += 1) {
        addedLength += Math.pow(arr2[i] - arr1[i], 2)
      }
      return Math.sqrt(addedLength)
    }

    function normalize(vec) {
      return div(vec, length(vec))
    }

    function rgbToHsl(val) {
      var r = val[0]
      var g = val[1]
      var b = val[2]
      var max = Math.max(r, g, b)
      var min = Math.min(r, g, b)
      var h
      var s
      var l = (max + min) / 2

      if (max === min) {
        h = 0 // achromatic
        s = 0 // achromatic
      } else {
        var d = max - min
        s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
        switch (max) {
          case r:
            h = (g - b) / d + (g < b ? 6 : 0)
            break
          case g:
            h = (b - r) / d + 2
            break
          case b:
            h = (r - g) / d + 4
            break
          default:
            break
        }
        h /= 6
      }

      return [h, s, l, val[3]]
    }

    function hue2rgb(p, q, t) {
      if (t < 0) t += 1
      if (t > 1) t -= 1
      if (t < 1 / 6) return p + (q - p) * 6 * t
      if (t < 1 / 2) return q
      if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6
      return p
    }

    function hslToRgb(val) {
      var h = val[0]
      var s = val[1]
      var l = val[2]

      var r
      var g
      var b

      if (s === 0) {
        r = l // achromatic
        b = l // achromatic
        g = l // achromatic
      } else {
        var q = l < 0.5 ? l * (1 + s) : l + s - l * s
        var p = 2 * l - q
        r = hue2rgb(p, q, h + 1 / 3)
        g = hue2rgb(p, q, h)
        b = hue2rgb(p, q, h - 1 / 3)
      }

      return [r, g, b, val[3]]
    }

    function linear(t, tMin, tMax, value1, value2) {
      if (value1 === undefined || value2 === undefined) {
        value1 = tMin
        value2 = tMax
        tMin = 0
        tMax = 1
      }
      if (tMax < tMin) {
        var _tMin = tMax
        tMax = tMin
        tMin = _tMin
      }
      if (t <= tMin) {
        return value1
      }
      if (t >= tMax) {
        return value2
      }
      var perc = tMax === tMin ? 0 : (t - tMin) / (tMax - tMin)
      if (!value1.length) {
        return value1 + (value2 - value1) * perc
      }
      var i
      var len = value1.length
      var arr = createTypedArray('float32', len)
      for (i = 0; i < len; i += 1) {
        arr[i] = value1[i] + (value2[i] - value1[i]) * perc
      }
      return arr
    }
    function random(min, max) {
      if (max === undefined) {
        if (min === undefined) {
          min = 0
          max = 1
        } else {
          max = min
          min = undefined
        }
      }
      if (max.length) {
        var i
        var len = max.length
        if (!min) {
          min = createTypedArray('float32', len)
        }
        var arr = createTypedArray('float32', len)
        var rnd = BMMath.random()
        for (i = 0; i < len; i += 1) {
          arr[i] = min[i] + rnd * (max[i] - min[i])
        }
        return arr
      }
      if (min === undefined) {
        min = 0
      }
      var rndm = BMMath.random()
      return min + rndm * (max - min)
    }

    function createPath(points, inTangents, outTangents, closed) {
      var i
      var len = points.length
      var path = shapePool.newElement()
      path.setPathData(!!closed, len)
      var arrPlaceholder = [0, 0]
      var inVertexPoint
      var outVertexPoint
      for (i = 0; i < len; i += 1) {
        inVertexPoint = inTangents && inTangents[i] ? inTangents[i] : arrPlaceholder
        outVertexPoint = outTangents && outTangents[i] ? outTangents[i] : arrPlaceholder
        path.setTripleAt(
          points[i][0],
          points[i][1],
          outVertexPoint[0] + points[i][0],
          outVertexPoint[1] + points[i][1],
          inVertexPoint[0] + points[i][0],
          inVertexPoint[1] + points[i][1],
          i,
          true,
        )
      }
      return path
    }

    // https://github.com/airbnb/lottie-web/issues/2828
    // https://github.com/airbnb/lottie-web/pull/2833
    // Bail out if we don't want expressions
    function noOp(_value) {
      return _value
    }
    function initiateExpression(elem, data, property) {
      return noOp
    }

    ob.initiateExpression = initiateExpression
    ob.__preventDeadCodeRemoval = [
      window,
      document,
      XMLHttpRequest,
      fetch,
      frames,
      $bm_neg,
      add,
      $bm_sum,
      $bm_sub,
      $bm_mul,
      $bm_div,
      $bm_mod,
      clamp,
      radians_to_degrees,
      degreesToRadians,
      degrees_to_radians,
      normalize,
      rgbToHsl,
      hslToRgb,
      linear,
      random,
      createPath,
    ]
    return ob
  })()

  const expressionHelpers = (function () {
    function searchExpressions(elem, data, prop) {
      if (data.x) {
        prop.k = true
        prop.x = true
        prop.initiateExpression = ExpressionManager.initiateExpression
        prop.effectsSequence.push(prop.initiateExpression(elem, data, prop).bind(prop))
      }
    }

    function getValueAtTime(frameNum) {
      frameNum *= this.elem.globalData.frameRate
      frameNum -= this.offsetTime
      if (frameNum !== this._cachingAtTime.lastFrame) {
        this._cachingAtTime.lastIndex = this._cachingAtTime.lastFrame < frameNum ? this._cachingAtTime.lastIndex : 0
        this._cachingAtTime.value = this.interpolateValue(frameNum, this._cachingAtTime)
        this._cachingAtTime.lastFrame = frameNum
      }
      return this._cachingAtTime.value
    }

    function getSpeedAtTime(frameNum) {
      var delta = -0.01
      var v1 = this.getValueAtTime(frameNum)
      var v2 = this.getValueAtTime(frameNum + delta)
      var speed = 0
      if (v1.length) {
        var i
        for (i = 0; i < v1.length; i += 1) {
          speed += Math.pow(v2[i] - v1[i], 2)
        }
        speed = Math.sqrt(speed) * 100
      } else {
        speed = 0
      }
      return speed
    }

    function getVelocityAtTime(frameNum) {
      if (this.vel !== undefined) {
        return this.vel
      }
      var delta = -0.001
      // frameNum += this.elem.data.st;
      var v1 = this.getValueAtTime(frameNum)
      var v2 = this.getValueAtTime(frameNum + delta)
      var velocity
      if (v1.length) {
        velocity = createTypedArray('float32', v1.length)
        var i
        for (i = 0; i < v1.length; i += 1) {
          // removing frameRate
          // if needed, don't add it here
          // velocity[i] = this.elem.globalData.frameRate*((v2[i] - v1[i])/delta);
          velocity[i] = (v2[i] - v1[i]) / delta
        }
      } else {
        velocity = (v2 - v1) / delta
      }
      return velocity
    }

    function getStaticValueAtTime() {
      return this.pv
    }

    function setGroupProperty(propertyGroup) {
      this.propertyGroup = propertyGroup
    }

    return {
      searchExpressions: searchExpressions,
      getSpeedAtTime: getSpeedAtTime,
      getVelocityAtTime: getVelocityAtTime,
      getValueAtTime: getValueAtTime,
      getStaticValueAtTime: getStaticValueAtTime,
      setGroupProperty: setGroupProperty,
    }
  })()

  function addPropertyDecorator() {
    function loopOut(type, duration, durationFlag) {
      if (!this.k || !this.keyframes) {
        return this.pv
      }
      type = type ? type.toLowerCase() : ''
      var currentFrame = this.comp.renderedFrame
      var keyframes = this.keyframes
      var lastKeyFrame = keyframes[keyframes.length - 1].t
      if (currentFrame <= lastKeyFrame) {
        return this.pv
      }
      var cycleDuration
      var firstKeyFrame
      if (!durationFlag) {
        if (!duration || duration > keyframes.length - 1) {
          duration = keyframes.length - 1
        }
        firstKeyFrame = keyframes[keyframes.length - 1 - duration].t
        cycleDuration = lastKeyFrame - firstKeyFrame
      } else {
        if (!duration) {
          cycleDuration = Math.max(0, lastKeyFrame - this.elem.data.ip)
        } else {
          cycleDuration = Math.abs(lastKeyFrame - this.elem.comp.globalData.frameRate * duration)
        }
        firstKeyFrame = lastKeyFrame - cycleDuration
      }
      var i
      var len
      var ret
      if (type === 'pingpong') {
        var iterations = Math.floor((currentFrame - firstKeyFrame) / cycleDuration)
        if (iterations % 2 !== 0) {
          return this.getValueAtTime(
            (cycleDuration - ((currentFrame - firstKeyFrame) % cycleDuration) + firstKeyFrame) /
              this.comp.globalData.frameRate,
            0,
          ) // eslint-disable-line
        }
      } else if (type === 'offset') {
        var initV = this.getValueAtTime(firstKeyFrame / this.comp.globalData.frameRate, 0)
        var endV = this.getValueAtTime(lastKeyFrame / this.comp.globalData.frameRate, 0)
        var current = this.getValueAtTime(
          (((currentFrame - firstKeyFrame) % cycleDuration) + firstKeyFrame) / this.comp.globalData.frameRate,
          0,
        ) // eslint-disable-line
        var repeats = Math.floor((currentFrame - firstKeyFrame) / cycleDuration)
        if (this.pv.length) {
          ret = new Array(initV.length)
          len = ret.length
          for (i = 0; i < len; i += 1) {
            ret[i] = (endV[i] - initV[i]) * repeats + current[i]
          }
          return ret
        }
        return (endV - initV) * repeats + current
      } else if (type === 'continue') {
        var lastValue = this.getValueAtTime(lastKeyFrame / this.comp.globalData.frameRate, 0)
        var nextLastValue = this.getValueAtTime((lastKeyFrame - 0.001) / this.comp.globalData.frameRate, 0)
        if (this.pv.length) {
          ret = new Array(lastValue.length)
          len = ret.length
          for (i = 0; i < len; i += 1) {
            ret[i] =
              lastValue[i] +
              ((lastValue[i] - nextLastValue[i]) * ((currentFrame - lastKeyFrame) / this.comp.globalData.frameRate)) /
                0.0005 // eslint-disable-line
          }
          return ret
        }
        return lastValue + (lastValue - nextLastValue) * ((currentFrame - lastKeyFrame) / 0.001)
      }
      return this.getValueAtTime(
        (((currentFrame - firstKeyFrame) % cycleDuration) + firstKeyFrame) / this.comp.globalData.frameRate,
        0,
      ) // eslint-disable-line
    }

    function loopIn(type, duration, durationFlag) {
      if (!this.k) {
        return this.pv
      }
      type = type ? type.toLowerCase() : ''
      var currentFrame = this.comp.renderedFrame
      var keyframes = this.keyframes
      var firstKeyFrame = keyframes[0].t
      if (currentFrame >= firstKeyFrame) {
        return this.pv
      }
      var cycleDuration
      var lastKeyFrame
      if (!durationFlag) {
        if (!duration || duration > keyframes.length - 1) {
          duration = keyframes.length - 1
        }
        lastKeyFrame = keyframes[duration].t
        cycleDuration = lastKeyFrame - firstKeyFrame
      } else {
        if (!duration) {
          cycleDuration = Math.max(0, this.elem.data.op - firstKeyFrame)
        } else {
          cycleDuration = Math.abs(this.elem.comp.globalData.frameRate * duration)
        }
        lastKeyFrame = firstKeyFrame + cycleDuration
      }
      var i
      var len
      var ret
      if (type === 'pingpong') {
        var iterations = Math.floor((firstKeyFrame - currentFrame) / cycleDuration)
        if (iterations % 2 === 0) {
          return this.getValueAtTime(
            (((firstKeyFrame - currentFrame) % cycleDuration) + firstKeyFrame) / this.comp.globalData.frameRate,
            0,
          ) // eslint-disable-line
        }
      } else if (type === 'offset') {
        var initV = this.getValueAtTime(firstKeyFrame / this.comp.globalData.frameRate, 0)
        var endV = this.getValueAtTime(lastKeyFrame / this.comp.globalData.frameRate, 0)
        var current = this.getValueAtTime(
          (cycleDuration - ((firstKeyFrame - currentFrame) % cycleDuration) + firstKeyFrame) /
            this.comp.globalData.frameRate,
          0,
        )
        var repeats = Math.floor((firstKeyFrame - currentFrame) / cycleDuration) + 1
        if (this.pv.length) {
          ret = new Array(initV.length)
          len = ret.length
          for (i = 0; i < len; i += 1) {
            ret[i] = current[i] - (endV[i] - initV[i]) * repeats
          }
          return ret
        }
        return current - (endV - initV) * repeats
      } else if (type === 'continue') {
        var firstValue = this.getValueAtTime(firstKeyFrame / this.comp.globalData.frameRate, 0)
        var nextFirstValue = this.getValueAtTime((firstKeyFrame + 0.001) / this.comp.globalData.frameRate, 0)
        if (this.pv.length) {
          ret = new Array(firstValue.length)
          len = ret.length
          for (i = 0; i < len; i += 1) {
            ret[i] = firstValue[i] + ((firstValue[i] - nextFirstValue[i]) * (firstKeyFrame - currentFrame)) / 0.001
          }
          return ret
        }
        return firstValue + ((firstValue - nextFirstValue) * (firstKeyFrame - currentFrame)) / 0.001
      }
      return this.getValueAtTime(
        (cycleDuration - (((firstKeyFrame - currentFrame) % cycleDuration) + firstKeyFrame)) /
          this.comp.globalData.frameRate,
        0,
      ) // eslint-disable-line
    }

    function smooth(width, samples) {
      if (!this.k) {
        return this.pv
      }
      width = (width || 0.4) * 0.5
      samples = Math.floor(samples || 5)
      if (samples <= 1) {
        return this.pv
      }
      var currentTime = this.comp.renderedFrame / this.comp.globalData.frameRate
      var initFrame = currentTime - width
      var endFrame = currentTime + width
      var sampleFrequency = samples > 1 ? (endFrame - initFrame) / (samples - 1) : 1
      var i = 0
      var j = 0
      var value
      if (this.pv.length) {
        value = createTypedArray('float32', this.pv.length)
      } else {
        value = 0
      }
      var sampleValue
      while (i < samples) {
        sampleValue = this.getValueAtTime(initFrame + i * sampleFrequency)
        if (this.pv.length) {
          for (j = 0; j < this.pv.length; j += 1) {
            value[j] += sampleValue[j]
          }
        } else {
          value += sampleValue
        }
        i += 1
      }
      if (this.pv.length) {
        for (j = 0; j < this.pv.length; j += 1) {
          value[j] /= samples
        }
      } else {
        value /= samples
      }
      return value
    }

    function getTransformValueAtTime(time) {
      if (!this._transformCachingAtTime) {
        this._transformCachingAtTime = {
          v: new Matrix(),
        }
      }
      /// /
      var matrix = this._transformCachingAtTime.v
      matrix.cloneFromProps(this.pre.props)
      if (this.appliedTransformations < 1) {
        var anchor = this.a.getValueAtTime(time)
        matrix.translate(-anchor[0] * this.a.mult, -anchor[1] * this.a.mult, anchor[2] * this.a.mult)
      }
      if (this.appliedTransformations < 2) {
        var scale = this.s.getValueAtTime(time)
        matrix.scale(scale[0] * this.s.mult, scale[1] * this.s.mult, scale[2] * this.s.mult)
      }
      if (this.sk && this.appliedTransformations < 3) {
        var skew = this.sk.getValueAtTime(time)
        var skewAxis = this.sa.getValueAtTime(time)
        matrix.skewFromAxis(-skew * this.sk.mult, skewAxis * this.sa.mult)
      }
      if (this.r && this.appliedTransformations < 4) {
        var rotation = this.r.getValueAtTime(time)
        matrix.rotate(-rotation * this.r.mult)
      } else if (!this.r && this.appliedTransformations < 4) {
        var rotationZ = this.rz.getValueAtTime(time)
        var rotationY = this.ry.getValueAtTime(time)
        var rotationX = this.rx.getValueAtTime(time)
        var orientation = this.or.getValueAtTime(time)
        matrix
          .rotateZ(-rotationZ * this.rz.mult)
          .rotateY(rotationY * this.ry.mult)
          .rotateX(rotationX * this.rx.mult)
          .rotateZ(-orientation[2] * this.or.mult)
          .rotateY(orientation[1] * this.or.mult)
          .rotateX(orientation[0] * this.or.mult)
      }
      if (this.data.p && this.data.p.s) {
        var positionX = this.px.getValueAtTime(time)
        var positionY = this.py.getValueAtTime(time)
        if (this.data.p.z) {
          var positionZ = this.pz.getValueAtTime(time)
          matrix.translate(positionX * this.px.mult, positionY * this.py.mult, -positionZ * this.pz.mult)
        } else {
          matrix.translate(positionX * this.px.mult, positionY * this.py.mult, 0)
        }
      } else {
        var position = this.p.getValueAtTime(time)
        matrix.translate(position[0] * this.p.mult, position[1] * this.p.mult, -position[2] * this.p.mult)
      }
      return matrix
      /// /
    }

    function getTransformStaticValueAtTime() {
      return this.v.clone(new Matrix())
    }

    var getTransformProperty = TransformPropertyFactory.getTransformProperty
    TransformPropertyFactory.getTransformProperty = function (elem, data, container) {
      var prop = getTransformProperty(elem, data, container)
      if (prop.dynamicProperties.length) {
        prop.getValueAtTime = getTransformValueAtTime.bind(prop)
      } else {
        prop.getValueAtTime = getTransformStaticValueAtTime.bind(prop)
      }
      prop.setGroupProperty = expressionHelpers.setGroupProperty
      return prop
    }

    var propertyGetProp = PropertyFactory.getProp
    PropertyFactory.getProp = function (elem, data, type, mult, container) {
      var prop = propertyGetProp(elem, data, type, mult, container)
      // prop.getVelocityAtTime = getVelocityAtTime;
      // prop.loopOut = loopOut;
      // prop.loopIn = loopIn;
      if (prop.kf) {
        prop.getValueAtTime = expressionHelpers.getValueAtTime.bind(prop)
      } else {
        prop.getValueAtTime = expressionHelpers.getStaticValueAtTime.bind(prop)
      }
      prop.setGroupProperty = expressionHelpers.setGroupProperty
      prop.loopOut = loopOut
      prop.loopIn = loopIn
      prop.smooth = smooth
      prop.getVelocityAtTime = expressionHelpers.getVelocityAtTime.bind(prop)
      prop.getSpeedAtTime = expressionHelpers.getSpeedAtTime.bind(prop)
      prop.numKeys = data.a === 1 ? data.k.length : 0
      prop.propertyIndex = data.ix
      var value = 0
      if (type !== 0) {
        value = createTypedArray('float32', data.a === 1 ? data.k[0].s.length : data.k.length)
      }
      prop._cachingAtTime = {
        lastFrame: initialDefaultFrame,
        lastIndex: 0,
        value: value,
      }
      expressionHelpers.searchExpressions(elem, data, prop)
      if (prop.k) {
        container.addDynamicProperty(prop)
      }

      return prop
    }

    function getShapeValueAtTime(frameNum) {
      // For now this caching object is created only when needed instead of creating it when the shape is initialized.
      if (!this._cachingAtTime) {
        this._cachingAtTime = {
          shapeValue: shapePool.clone(this.pv),
          lastIndex: 0,
          lastTime: initialDefaultFrame,
        }
      }

      frameNum *= this.elem.globalData.frameRate
      frameNum -= this.offsetTime
      if (frameNum !== this._cachingAtTime.lastTime) {
        this._cachingAtTime.lastIndex = this._cachingAtTime.lastTime < frameNum ? this._caching.lastIndex : 0
        this._cachingAtTime.lastTime = frameNum
        this.interpolateShape(frameNum, this._cachingAtTime.shapeValue, this._cachingAtTime)
      }
      return this._cachingAtTime.shapeValue
    }

    var ShapePropertyConstructorFunction = ShapePropertyFactory.getConstructorFunction()
    var KeyframedShapePropertyConstructorFunction = ShapePropertyFactory.getKeyframedConstructorFunction()

    function ShapeExpressions() {}
    ShapeExpressions.prototype = {
      vertices: function (prop, time) {
        if (this.k) {
          this.getValue()
        }
        var shapePath = this.v
        if (time !== undefined) {
          shapePath = this.getValueAtTime(time, 0)
        }
        var i
        var len = shapePath._length
        var vertices = shapePath[prop]
        var points = shapePath.v
        var arr = createSizedArray(len)
        for (i = 0; i < len; i += 1) {
          if (prop === 'i' || prop === 'o') {
            arr[i] = [vertices[i][0] - points[i][0], vertices[i][1] - points[i][1]]
          } else {
            arr[i] = [vertices[i][0], vertices[i][1]]
          }
        }
        return arr
      },
      points: function (time) {
        return this.vertices('v', time)
      },
      inTangents: function (time) {
        return this.vertices('i', time)
      },
      outTangents: function (time) {
        return this.vertices('o', time)
      },
      isClosed: function () {
        return this.v.c
      },
      pointOnPath: function (perc, time) {
        var shapePath = this.v
        if (time !== undefined) {
          shapePath = this.getValueAtTime(time, 0)
        }
        if (!this._segmentsLength) {
          this._segmentsLength = bez.getSegmentsLength(shapePath)
        }

        var segmentsLength = this._segmentsLength
        var lengths = segmentsLength.lengths
        var lengthPos = segmentsLength.totalLength * perc
        var i = 0
        var len = lengths.length
        var accumulatedLength = 0
        var pt
        while (i < len) {
          if (accumulatedLength + lengths[i].addedLength > lengthPos) {
            var initIndex = i
            var endIndex = shapePath.c && i === len - 1 ? 0 : i + 1
            var segmentPerc = (lengthPos - accumulatedLength) / lengths[i].addedLength
            pt = bez.getPointInSegment(
              shapePath.v[initIndex],
              shapePath.v[endIndex],
              shapePath.o[initIndex],
              shapePath.i[endIndex],
              segmentPerc,
              lengths[i],
            )
            break
          } else {
            accumulatedLength += lengths[i].addedLength
          }
          i += 1
        }
        if (!pt) {
          pt = shapePath.c
            ? [shapePath.v[0][0], shapePath.v[0][1]]
            : [shapePath.v[shapePath._length - 1][0], shapePath.v[shapePath._length - 1][1]]
        }
        return pt
      },
      vectorOnPath: function (perc, time, vectorType) {
        // perc doesn't use triple equality because it can be a Number object as well as a primitive.
        if (perc == 1) {
          // eslint-disable-line eqeqeq
          perc = this.v.c
        } else if (perc == 0) {
          // eslint-disable-line eqeqeq
          perc = 0.999
        }
        var pt1 = this.pointOnPath(perc, time)
        var pt2 = this.pointOnPath(perc + 0.001, time)
        var xLength = pt2[0] - pt1[0]
        var yLength = pt2[1] - pt1[1]
        var magnitude = Math.sqrt(Math.pow(xLength, 2) + Math.pow(yLength, 2))
        if (magnitude === 0) {
          return [0, 0]
        }
        var unitVector =
          vectorType === 'tangent'
            ? [xLength / magnitude, yLength / magnitude]
            : [-yLength / magnitude, xLength / magnitude]
        return unitVector
      },
      tangentOnPath: function (perc, time) {
        return this.vectorOnPath(perc, time, 'tangent')
      },
      normalOnPath: function (perc, time) {
        return this.vectorOnPath(perc, time, 'normal')
      },
      setGroupProperty: expressionHelpers.setGroupProperty,
      getValueAtTime: expressionHelpers.getStaticValueAtTime,
    }
    extendPrototype([ShapeExpressions], ShapePropertyConstructorFunction)
    extendPrototype([ShapeExpressions], KeyframedShapePropertyConstructorFunction)
    KeyframedShapePropertyConstructorFunction.prototype.getValueAtTime = getShapeValueAtTime
    KeyframedShapePropertyConstructorFunction.prototype.initiateExpression = ExpressionManager.initiateExpression

    var propertyGetShapeProp = ShapePropertyFactory.getShapeProp
    ShapePropertyFactory.getShapeProp = function (elem, data, type, arr, trims) {
      var prop = propertyGetShapeProp(elem, data, type, arr, trims)
      prop.propertyIndex = data.ix
      prop.lock = false
      if (type === 3) {
        expressionHelpers.searchExpressions(elem, data.pt, prop)
      } else if (type === 4) {
        expressionHelpers.searchExpressions(elem, data.ks, prop)
      }
      if (prop.k) {
        elem.addDynamicProperty(prop)
      }
      return prop
    }
  }

  function initialize$1() {
    addPropertyDecorator()
  }

  function addDecorator() {
    function searchExpressions() {
      if (this.data.d.x) {
        this.calculateExpression = ExpressionManager.initiateExpression.bind(this)(this.elem, this.data.d, this)
        this.addEffect(this.getExpressionValue.bind(this))
        return true
      }
      return null
    }

    TextProperty.prototype.getExpressionValue = function (currentValue, text) {
      var newValue = this.calculateExpression(text)
      if (currentValue.t !== newValue) {
        var newData = {}
        this.copyData(newData, currentValue)
        newData.t = newValue.toString()
        newData.__complete = false
        return newData
      }
      return currentValue
    }

    TextProperty.prototype.searchProperty = function () {
      var isKeyframed = this.searchKeyframes()
      var hasExpressions = this.searchExpressions()
      this.kf = isKeyframed || hasExpressions
      return this.kf
    }

    TextProperty.prototype.searchExpressions = searchExpressions
  }

  function initialize() {
    addDecorator()
  }

  // Registering expression plugin
  setExpressionsPlugin(Expressions)
  initialize$1()
  initialize()

  return lottie
})()

export { lottie as default }
